steerdev 1.0.37__tar.gz → 1.0.52__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.
- {steerdev-1.0.37 → steerdev-1.0.52}/PKG-INFO +1 -1
- {steerdev-1.0.37 → steerdev-1.0.52}/pyproject.toml +1 -1
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/agent_loop.py +94 -25
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/__init__.py +2 -0
- steerdev-1.0.52/src/steerdev_agent/api/reports.py +82 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/cli.py +82 -8
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/models.py +54 -4
- steerdev-1.0.52/src/steerdev_agent/evidence.py +156 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/__init__.py +4 -4
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/claude.py +9 -7
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/runner.py +268 -23
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/claude_setup.py +114 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/repo_setup.py +20 -10
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/settings.json +6 -1
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +7 -3
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +7 -3
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/steerdev.yaml +16 -0
- steerdev-1.0.52/src/steerdev_agent/setup/templates/worktrunk.config.toml +40 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/executor.py +29 -3
- steerdev-1.0.52/src/steerdev_agent/worktree.py +264 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_agent_loop.py +8 -2
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_agent_loop_extended.py +13 -6
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_claude_executor.py +13 -6
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_claude_setup.py +44 -2
- steerdev-1.0.52/tests/test_reports_client.py +115 -0
- steerdev-1.0.52/tests/test_runner_worktrees.py +92 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_tasks.py +6 -6
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workspace_extended.py +2 -5
- steerdev-1.0.52/tests/test_worktree.py +153 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/.github/workflows/pre-commit.yml +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/.github/workflows/publish.yml +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/.gitignore +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/.pre-commit-config.yaml +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/AGENTS.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/CLAUDE.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/README.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/scripts/pre-commit-version-bump.sh +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/activity.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/agents.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/canals.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/client.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/commands.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/configs.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/context.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/events.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/hooks.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/implementation_plan.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/messages.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/prd.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/runs.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/sessions.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/specs.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/tasks.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/workflow_runs.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/api/workflows.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/platform.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/config/settings.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/base.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/executor/stream.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/handlers/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/handlers/prd.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/integration.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/builder.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/templates.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/prompt/workflow_template.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/py.typed +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/retry.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/update_check.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/version.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/context.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workflow/memory.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workspace/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workspace/project_manager.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/src/steerdev_agent/workspace/tool_detection.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/__init__.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_agents_api.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_api_client.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_client_methods.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_commands_api.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_config.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_config_extended.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_context_search.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_executor.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_platform_config.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_prompt.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_retry.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_runner_merge_modes.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_stream_parser.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_version.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workflow_context.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workflow_memory.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workflow_prompt_template.py +0 -0
- {steerdev-1.0.37 → steerdev-1.0.52}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: steerdev
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.52
|
|
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
|
|
@@ -36,9 +36,11 @@ from steerdev_agent.api.sessions import SessionCreateRequest, SessionsClient
|
|
|
36
36
|
from steerdev_agent.api.tasks import TasksClient
|
|
37
37
|
from steerdev_agent.config.models import (
|
|
38
38
|
AgentLoopConfig,
|
|
39
|
+
EvidenceConfig,
|
|
39
40
|
ExecutorConfig,
|
|
40
41
|
RetryConfig,
|
|
41
42
|
WorkspaceConfig,
|
|
43
|
+
WorktreeConfig,
|
|
42
44
|
)
|
|
43
45
|
from steerdev_agent.executor import ExecutorFactory
|
|
44
46
|
from steerdev_agent.executor.base import EventType
|
|
@@ -60,7 +62,7 @@ class CommandExecutor:
|
|
|
60
62
|
"""Shared command execution dispatch for both project and workspace agents.
|
|
61
63
|
|
|
62
64
|
Subclasses provide: _api_key, agent_type, agent_name, model, max_turns,
|
|
63
|
-
_executor_config, _workflow_id,
|
|
65
|
+
_executor_config, _workflow_id, _worktree_config, _agent_loop_config,
|
|
64
66
|
_commands_client, _sessions_client, _shutdown_event, and the stats counters.
|
|
65
67
|
"""
|
|
66
68
|
|
|
@@ -75,11 +77,14 @@ class CommandExecutor:
|
|
|
75
77
|
_workflow_id: str | None
|
|
76
78
|
_enable_waves: bool
|
|
77
79
|
_enable_canals: bool
|
|
78
|
-
|
|
80
|
+
_worktree_config: WorktreeConfig
|
|
81
|
+
_evidence_config: EvidenceConfig
|
|
79
82
|
_agent_loop_config: AgentLoopConfig
|
|
80
83
|
_commands_client: CommandsClient | None
|
|
81
84
|
_sessions_client: SessionsClient | None
|
|
82
85
|
_shutdown_event: asyncio.Event
|
|
86
|
+
_current_task: asyncio.Task[bool] | None
|
|
87
|
+
_signal_count: int
|
|
83
88
|
_commands_executed: int
|
|
84
89
|
_commands_succeeded: int
|
|
85
90
|
_commands_failed: int
|
|
@@ -169,9 +174,11 @@ class CommandExecutor:
|
|
|
169
174
|
max_turns=self.max_turns,
|
|
170
175
|
enable_waves=self._enable_waves,
|
|
171
176
|
enable_canals=self._enable_canals,
|
|
172
|
-
|
|
177
|
+
worktree_config=self._worktree_config,
|
|
178
|
+
evidence_config=self._evidence_config,
|
|
173
179
|
executor_config=self._executor_config,
|
|
174
180
|
force_workflow_id=None,
|
|
181
|
+
shutdown_event=self._shutdown_event,
|
|
175
182
|
)
|
|
176
183
|
|
|
177
184
|
# Fetch task details
|
|
@@ -431,7 +438,8 @@ class AgentLoop(CommandExecutor):
|
|
|
431
438
|
force_workflow_id: str | None = None,
|
|
432
439
|
enable_waves: bool = True,
|
|
433
440
|
enable_canals: bool = False,
|
|
434
|
-
|
|
441
|
+
worktree_config: WorktreeConfig | None = None,
|
|
442
|
+
evidence_config: EvidenceConfig | None = None,
|
|
435
443
|
retry_config: RetryConfig | None = None,
|
|
436
444
|
) -> None:
|
|
437
445
|
self.project_id = project_id
|
|
@@ -447,11 +455,14 @@ class AgentLoop(CommandExecutor):
|
|
|
447
455
|
self._workflow_id = force_workflow_id
|
|
448
456
|
self._enable_waves = enable_waves
|
|
449
457
|
self._enable_canals = enable_canals
|
|
450
|
-
self.
|
|
458
|
+
self._worktree_config = worktree_config or WorktreeConfig()
|
|
459
|
+
self._evidence_config = evidence_config or EvidenceConfig()
|
|
451
460
|
|
|
452
461
|
# State
|
|
453
462
|
self._agent_id: str | None = None
|
|
454
463
|
self._shutdown_event = asyncio.Event()
|
|
464
|
+
self._current_task: asyncio.Task[bool] | None = None
|
|
465
|
+
self._signal_count = 0
|
|
455
466
|
self._is_busy = False
|
|
456
467
|
self._consecutive_errors = 0
|
|
457
468
|
self._commands_executed = 0
|
|
@@ -470,7 +481,11 @@ class AgentLoop(CommandExecutor):
|
|
|
470
481
|
workflow_status = self._workflow_id or "single-phase"
|
|
471
482
|
waves_status = "enabled" if self._enable_waves else "disabled"
|
|
472
483
|
canals_status = "enabled" if self._enable_canals else "disabled"
|
|
473
|
-
worktree_status =
|
|
484
|
+
worktree_status = (
|
|
485
|
+
f"enabled ({self._worktree_config.provider})"
|
|
486
|
+
if self._worktree_config.enabled
|
|
487
|
+
else "disabled"
|
|
488
|
+
)
|
|
474
489
|
|
|
475
490
|
console.print(
|
|
476
491
|
Panel(
|
|
@@ -577,7 +592,14 @@ class AgentLoop(CommandExecutor):
|
|
|
577
592
|
|
|
578
593
|
while not self._shutdown_event.is_set():
|
|
579
594
|
try:
|
|
580
|
-
|
|
595
|
+
task = asyncio.create_task(self._poll_once())
|
|
596
|
+
self._current_task = task
|
|
597
|
+
try:
|
|
598
|
+
executed = await task
|
|
599
|
+
except asyncio.CancelledError:
|
|
600
|
+
break
|
|
601
|
+
finally:
|
|
602
|
+
self._current_task = None
|
|
581
603
|
|
|
582
604
|
if executed:
|
|
583
605
|
self._consecutive_errors = 0
|
|
@@ -696,13 +718,27 @@ class AgentLoop(CommandExecutor):
|
|
|
696
718
|
"""Set up SIGINT/SIGTERM handlers for graceful shutdown."""
|
|
697
719
|
loop = asyncio.get_running_loop()
|
|
698
720
|
|
|
699
|
-
def
|
|
700
|
-
|
|
701
|
-
|
|
721
|
+
def _sigint_handler() -> None:
|
|
722
|
+
self._signal_count += 1
|
|
723
|
+
if self._signal_count == 1:
|
|
724
|
+
logger.info("Received shutdown signal")
|
|
725
|
+
console.print(
|
|
726
|
+
"\n[yellow]Received shutdown signal, finishing current work...[/yellow]"
|
|
727
|
+
)
|
|
728
|
+
console.print("[dim]Press Ctrl-C again to force stop[/dim]")
|
|
729
|
+
self._shutdown_event.set()
|
|
730
|
+
else:
|
|
731
|
+
logger.info("Force stop requested")
|
|
732
|
+
console.print("\n[red]Force stopping...[/red]")
|
|
733
|
+
if self._current_task and not self._current_task.done():
|
|
734
|
+
self._current_task.cancel()
|
|
735
|
+
|
|
736
|
+
def _sigterm_handler() -> None:
|
|
737
|
+
logger.info("Received SIGTERM")
|
|
702
738
|
self._shutdown_event.set()
|
|
703
739
|
|
|
704
|
-
|
|
705
|
-
|
|
740
|
+
loop.add_signal_handler(signal.SIGINT, _sigint_handler)
|
|
741
|
+
loop.add_signal_handler(signal.SIGTERM, _sigterm_handler)
|
|
706
742
|
|
|
707
743
|
async def _shutdown(self) -> None:
|
|
708
744
|
"""Clean up resources and send offline heartbeat."""
|
|
@@ -769,7 +805,8 @@ class WorkspaceAgentLoop(CommandExecutor):
|
|
|
769
805
|
force_workflow_id: str | None = None,
|
|
770
806
|
enable_waves: bool = True,
|
|
771
807
|
enable_canals: bool = False,
|
|
772
|
-
|
|
808
|
+
worktree_config: WorktreeConfig | None = None,
|
|
809
|
+
evidence_config: EvidenceConfig | None = None,
|
|
773
810
|
retry_config: RetryConfig | None = None,
|
|
774
811
|
) -> None:
|
|
775
812
|
self.workspace_path = Path(workspace_path)
|
|
@@ -785,11 +822,14 @@ class WorkspaceAgentLoop(CommandExecutor):
|
|
|
785
822
|
self._workflow_id = force_workflow_id
|
|
786
823
|
self._enable_waves = enable_waves
|
|
787
824
|
self._enable_canals = enable_canals
|
|
788
|
-
self.
|
|
825
|
+
self._worktree_config = worktree_config or WorktreeConfig()
|
|
826
|
+
self._evidence_config = evidence_config or EvidenceConfig()
|
|
789
827
|
|
|
790
828
|
# State
|
|
791
829
|
self._agent_id: str | None = None
|
|
792
830
|
self._shutdown_event = asyncio.Event()
|
|
831
|
+
self._current_task: asyncio.Task[bool] | None = None
|
|
832
|
+
self._signal_count = 0
|
|
793
833
|
self._is_busy = False
|
|
794
834
|
self._consecutive_errors = 0
|
|
795
835
|
self._commands_executed = 0
|
|
@@ -813,7 +853,11 @@ class WorkspaceAgentLoop(CommandExecutor):
|
|
|
813
853
|
|
|
814
854
|
waves_status = "enabled" if self._enable_waves else "disabled"
|
|
815
855
|
canals_status = "enabled" if self._enable_canals else "disabled"
|
|
816
|
-
worktree_status =
|
|
856
|
+
worktree_status = (
|
|
857
|
+
f"enabled ({self._worktree_config.provider})"
|
|
858
|
+
if self._worktree_config.enabled
|
|
859
|
+
else "disabled"
|
|
860
|
+
)
|
|
817
861
|
|
|
818
862
|
console.print(
|
|
819
863
|
Panel(
|
|
@@ -956,7 +1000,14 @@ class WorkspaceAgentLoop(CommandExecutor):
|
|
|
956
1000
|
|
|
957
1001
|
while not self._shutdown_event.is_set():
|
|
958
1002
|
try:
|
|
959
|
-
|
|
1003
|
+
task = asyncio.create_task(self._poll_once())
|
|
1004
|
+
self._current_task = task
|
|
1005
|
+
try:
|
|
1006
|
+
executed = await task
|
|
1007
|
+
except asyncio.CancelledError:
|
|
1008
|
+
break
|
|
1009
|
+
finally:
|
|
1010
|
+
self._current_task = None
|
|
960
1011
|
|
|
961
1012
|
if executed:
|
|
962
1013
|
self._consecutive_errors = 0
|
|
@@ -1099,13 +1150,27 @@ class WorkspaceAgentLoop(CommandExecutor):
|
|
|
1099
1150
|
"""Set up SIGINT/SIGTERM handlers for graceful shutdown."""
|
|
1100
1151
|
loop = asyncio.get_running_loop()
|
|
1101
1152
|
|
|
1102
|
-
def
|
|
1103
|
-
|
|
1104
|
-
|
|
1153
|
+
def _sigint_handler() -> None:
|
|
1154
|
+
self._signal_count += 1
|
|
1155
|
+
if self._signal_count == 1:
|
|
1156
|
+
logger.info("Received shutdown signal")
|
|
1157
|
+
console.print(
|
|
1158
|
+
"\n[yellow]Received shutdown signal, finishing current work...[/yellow]"
|
|
1159
|
+
)
|
|
1160
|
+
console.print("[dim]Press Ctrl-C again to force stop[/dim]")
|
|
1161
|
+
self._shutdown_event.set()
|
|
1162
|
+
else:
|
|
1163
|
+
logger.info("Force stop requested")
|
|
1164
|
+
console.print("\n[red]Force stopping...[/red]")
|
|
1165
|
+
if self._current_task and not self._current_task.done():
|
|
1166
|
+
self._current_task.cancel()
|
|
1167
|
+
|
|
1168
|
+
def _sigterm_handler() -> None:
|
|
1169
|
+
logger.info("Received SIGTERM")
|
|
1105
1170
|
self._shutdown_event.set()
|
|
1106
1171
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1172
|
+
loop.add_signal_handler(signal.SIGINT, _sigint_handler)
|
|
1173
|
+
loop.add_signal_handler(signal.SIGTERM, _sigterm_handler)
|
|
1109
1174
|
|
|
1110
1175
|
async def _shutdown(self) -> None:
|
|
1111
1176
|
"""Clean up resources and send offline heartbeat."""
|
|
@@ -1162,7 +1227,8 @@ async def run_agent_loop(
|
|
|
1162
1227
|
force_workflow_id: str | None = None,
|
|
1163
1228
|
enable_waves: bool = True,
|
|
1164
1229
|
enable_canals: bool = False,
|
|
1165
|
-
|
|
1230
|
+
worktree_config: WorktreeConfig | None = None,
|
|
1231
|
+
evidence_config: EvidenceConfig | None = None,
|
|
1166
1232
|
retry_config: RetryConfig | None = None,
|
|
1167
1233
|
) -> None:
|
|
1168
1234
|
"""Run the project-scoped agent loop.
|
|
@@ -1182,7 +1248,8 @@ async def run_agent_loop(
|
|
|
1182
1248
|
force_workflow_id=force_workflow_id,
|
|
1183
1249
|
enable_waves=enable_waves,
|
|
1184
1250
|
enable_canals=enable_canals,
|
|
1185
|
-
|
|
1251
|
+
worktree_config=worktree_config,
|
|
1252
|
+
evidence_config=evidence_config,
|
|
1186
1253
|
retry_config=retry_config,
|
|
1187
1254
|
)
|
|
1188
1255
|
await agent.start()
|
|
@@ -1201,7 +1268,8 @@ async def run_workspace_agent_loop(
|
|
|
1201
1268
|
force_workflow_id: str | None = None,
|
|
1202
1269
|
enable_waves: bool = True,
|
|
1203
1270
|
enable_canals: bool = False,
|
|
1204
|
-
|
|
1271
|
+
worktree_config: WorktreeConfig | None = None,
|
|
1272
|
+
evidence_config: EvidenceConfig | None = None,
|
|
1205
1273
|
retry_config: RetryConfig | None = None,
|
|
1206
1274
|
) -> None:
|
|
1207
1275
|
"""Run the workspace (multi-project) agent loop.
|
|
@@ -1221,7 +1289,8 @@ async def run_workspace_agent_loop(
|
|
|
1221
1289
|
force_workflow_id=force_workflow_id,
|
|
1222
1290
|
enable_waves=enable_waves,
|
|
1223
1291
|
enable_canals=enable_canals,
|
|
1224
|
-
|
|
1292
|
+
worktree_config=worktree_config,
|
|
1293
|
+
evidence_config=evidence_config,
|
|
1225
1294
|
retry_config=retry_config,
|
|
1226
1295
|
)
|
|
1227
1296
|
await agent.start()
|
|
@@ -4,6 +4,7 @@ from steerdev_agent.api.client import SteerDevClient, get_api_key, get_project_i
|
|
|
4
4
|
from steerdev_agent.api.configs import ConfigsClient
|
|
5
5
|
from steerdev_agent.api.events import EventData, EventsClient
|
|
6
6
|
from steerdev_agent.api.hooks import HooksClient
|
|
7
|
+
from steerdev_agent.api.reports import ReportsClient
|
|
7
8
|
from steerdev_agent.api.runs import RunCreateRequest, RunResponse, RunsClient
|
|
8
9
|
from steerdev_agent.api.sessions import (
|
|
9
10
|
SessionCreateRequest,
|
|
@@ -18,6 +19,7 @@ __all__ = [
|
|
|
18
19
|
"EventData",
|
|
19
20
|
"EventsClient",
|
|
20
21
|
"HooksClient",
|
|
22
|
+
"ReportsClient",
|
|
21
23
|
"RunCreateRequest",
|
|
22
24
|
"RunResponse",
|
|
23
25
|
"RunsClient",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Evidence reports API client for SteerDev Agent.
|
|
2
|
+
|
|
3
|
+
Submits evidence reports to the platform after task/workflow completion,
|
|
4
|
+
making work visible on the project review dashboard.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from loguru import logger
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
|
|
14
|
+
from steerdev_agent.api.client import SteerDevClient
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ReportsClient(SteerDevClient):
|
|
20
|
+
"""Client for submitting evidence reports to the platform.
|
|
21
|
+
|
|
22
|
+
Evidence reports capture task completion summaries, making agent work
|
|
23
|
+
visible on the project review page (/projects/[id]/review).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def submit(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
project_id: str,
|
|
30
|
+
summary: str,
|
|
31
|
+
blocks: list[dict[str, Any]],
|
|
32
|
+
task_id: str | None = None,
|
|
33
|
+
wave_id: str | None = None,
|
|
34
|
+
agent_id: str | None = None,
|
|
35
|
+
evaluation_steps: list[dict[str, Any]] | None = None,
|
|
36
|
+
) -> dict[str, Any] | None:
|
|
37
|
+
"""Submit an evidence report for a completed task or workflow.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
project_id: SteerDev project ID.
|
|
41
|
+
summary: Report title/summary (required for reviewability).
|
|
42
|
+
blocks: List of report blocks (type, content, order).
|
|
43
|
+
task_id: Associated task ID.
|
|
44
|
+
wave_id: Associated wave ID.
|
|
45
|
+
agent_id: Associated agent ID.
|
|
46
|
+
evaluation_steps: Optional evaluation steps (title, result, order).
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Response dict with report_id and is_reviewable, or None on failure.
|
|
50
|
+
"""
|
|
51
|
+
payload: dict[str, Any] = {
|
|
52
|
+
"project_id": project_id,
|
|
53
|
+
"summary": summary,
|
|
54
|
+
"blocks": blocks,
|
|
55
|
+
}
|
|
56
|
+
if task_id:
|
|
57
|
+
payload["task_id"] = task_id
|
|
58
|
+
if wave_id:
|
|
59
|
+
payload["wave_id"] = wave_id
|
|
60
|
+
if agent_id:
|
|
61
|
+
payload["agent_id"] = agent_id
|
|
62
|
+
if evaluation_steps:
|
|
63
|
+
payload["evaluation_steps"] = evaluation_steps
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
response = self.post("/reports", json=payload)
|
|
67
|
+
if response.status_code in (200, 201):
|
|
68
|
+
data = response.json()
|
|
69
|
+
report_id = data.get("report_id", "unknown")
|
|
70
|
+
is_reviewable = data.get("is_reviewable", False)
|
|
71
|
+
console.print(
|
|
72
|
+
f"[dim]Evidence report submitted: {report_id} "
|
|
73
|
+
f"(reviewable: {is_reviewable})[/dim]"
|
|
74
|
+
)
|
|
75
|
+
return data
|
|
76
|
+
logger.warning(
|
|
77
|
+
f"Evidence report submission failed: {response.status_code} - {response.text}"
|
|
78
|
+
)
|
|
79
|
+
return None
|
|
80
|
+
except Exception:
|
|
81
|
+
logger.debug("Evidence report submission error", exc_info=True)
|
|
82
|
+
return None
|
|
@@ -1509,7 +1509,10 @@ def _resolve_run_config(
|
|
|
1509
1509
|
resolved_model = model if model is not None else config.agent.model
|
|
1510
1510
|
resolved_max_turns = max_turns if max_turns is not None else config.agent.max_turns
|
|
1511
1511
|
resolved_timeout = timeout if timeout is not None else config.agent.timeout_seconds
|
|
1512
|
-
|
|
1512
|
+
# Resolve worktree config: CLI flag overrides config file enabled state
|
|
1513
|
+
resolved_worktree_config = config.worktrees.model_copy()
|
|
1514
|
+
if worktrees is not None:
|
|
1515
|
+
resolved_worktree_config.enabled = worktrees
|
|
1513
1516
|
|
|
1514
1517
|
resolved_api_key = api_key
|
|
1515
1518
|
if not resolved_api_key:
|
|
@@ -1528,7 +1531,7 @@ def _resolve_run_config(
|
|
|
1528
1531
|
"timeout_seconds": resolved_timeout,
|
|
1529
1532
|
"enable_waves": waves,
|
|
1530
1533
|
"enable_canals": canals,
|
|
1531
|
-
"
|
|
1534
|
+
"worktree_config": resolved_worktree_config,
|
|
1532
1535
|
"force_workflow_id": resolved_workflow_id,
|
|
1533
1536
|
"dry_run": dry_run,
|
|
1534
1537
|
}
|
|
@@ -1612,6 +1615,13 @@ def run(
|
|
|
1612
1615
|
help="Enable git worktree isolation (default: from config or disabled)",
|
|
1613
1616
|
),
|
|
1614
1617
|
] = None,
|
|
1618
|
+
evidence: Annotated[
|
|
1619
|
+
bool | None,
|
|
1620
|
+
typer.Option(
|
|
1621
|
+
"--evidence/--no-evidence",
|
|
1622
|
+
help="Submit evidence reports after task completion (default: from config or disabled)",
|
|
1623
|
+
),
|
|
1624
|
+
] = None,
|
|
1615
1625
|
dry_run: Annotated[
|
|
1616
1626
|
bool,
|
|
1617
1627
|
typer.Option(
|
|
@@ -1700,7 +1710,14 @@ def run(
|
|
|
1700
1710
|
resolved_model = model if model is not None else config.agent.model
|
|
1701
1711
|
resolved_max_turns = max_turns if max_turns is not None else config.agent.max_turns
|
|
1702
1712
|
resolved_timeout = timeout if timeout is not None else config.agent.timeout_seconds
|
|
1703
|
-
|
|
1713
|
+
# Resolve worktree config: CLI flag overrides config file enabled state
|
|
1714
|
+
resolved_worktree_config = config.worktrees.model_copy()
|
|
1715
|
+
if worktrees is not None:
|
|
1716
|
+
resolved_worktree_config.enabled = worktrees
|
|
1717
|
+
# Resolve evidence config: CLI flag overrides config file enabled state
|
|
1718
|
+
resolved_evidence_config = config.evidence.model_copy()
|
|
1719
|
+
if evidence is not None:
|
|
1720
|
+
resolved_evidence_config.enabled = evidence
|
|
1704
1721
|
|
|
1705
1722
|
# API key: CLI > env (via envvar) > config env var
|
|
1706
1723
|
resolved_api_key = api_key
|
|
@@ -1711,7 +1728,12 @@ def run(
|
|
|
1711
1728
|
if not resolved_workflow_id:
|
|
1712
1729
|
resolved_workflow_id = config.agent.workflow_id
|
|
1713
1730
|
|
|
1714
|
-
worktree_status =
|
|
1731
|
+
worktree_status = (
|
|
1732
|
+
f"enabled ({resolved_worktree_config.provider})"
|
|
1733
|
+
if resolved_worktree_config.enabled
|
|
1734
|
+
else "disabled"
|
|
1735
|
+
)
|
|
1736
|
+
evidence_status = "enabled" if resolved_evidence_config.enabled else "disabled"
|
|
1715
1737
|
dry_run_status = "enabled" if dry_run else "disabled"
|
|
1716
1738
|
waves_status = "enabled" if waves else "disabled"
|
|
1717
1739
|
canals_status = "enabled" if canals else "disabled"
|
|
@@ -1732,6 +1754,7 @@ def run(
|
|
|
1732
1754
|
f"Waves: {waves_status}\n"
|
|
1733
1755
|
f"Canals: {canals_status}\n"
|
|
1734
1756
|
f"Worktrees: {worktree_status}\n"
|
|
1757
|
+
f"Evidence: {evidence_status}\n"
|
|
1735
1758
|
f"Dry Run: {dry_run_status}",
|
|
1736
1759
|
title="Starting",
|
|
1737
1760
|
)
|
|
@@ -1762,7 +1785,8 @@ def run(
|
|
|
1762
1785
|
timeout_seconds=resolved_timeout,
|
|
1763
1786
|
enable_waves=waves,
|
|
1764
1787
|
enable_canals=canals,
|
|
1765
|
-
|
|
1788
|
+
worktree_config=resolved_worktree_config,
|
|
1789
|
+
evidence_config=resolved_evidence_config,
|
|
1766
1790
|
force_workflow_id=resolved_workflow_id,
|
|
1767
1791
|
dry_run=dry_run,
|
|
1768
1792
|
retry_config=retry_config,
|
|
@@ -1911,6 +1935,13 @@ def agent(
|
|
|
1911
1935
|
help="Enable canal merge flow selection (default: disabled)",
|
|
1912
1936
|
),
|
|
1913
1937
|
] = False,
|
|
1938
|
+
evidence: Annotated[
|
|
1939
|
+
bool | None,
|
|
1940
|
+
typer.Option(
|
|
1941
|
+
"--evidence/--no-evidence",
|
|
1942
|
+
help="Submit evidence reports after task completion (default: from config or disabled)",
|
|
1943
|
+
),
|
|
1944
|
+
] = None,
|
|
1914
1945
|
) -> None:
|
|
1915
1946
|
"""Run the agent in persistent mode.
|
|
1916
1947
|
|
|
@@ -1970,7 +2001,10 @@ def agent(
|
|
|
1970
2001
|
if gap_seconds is not None:
|
|
1971
2002
|
agent_loop_config.gap_seconds = gap_seconds
|
|
1972
2003
|
|
|
1973
|
-
|
|
2004
|
+
resolved_worktree_config = config.worktrees.model_copy()
|
|
2005
|
+
resolved_evidence_config = config.evidence.model_copy()
|
|
2006
|
+
if evidence is not None:
|
|
2007
|
+
resolved_evidence_config.enabled = evidence
|
|
1974
2008
|
|
|
1975
2009
|
# Resolve retry config: CLI > config > default
|
|
1976
2010
|
retry_config = config.retry.model_copy()
|
|
@@ -2002,7 +2036,8 @@ def agent(
|
|
|
2002
2036
|
force_workflow_id=workflow_id,
|
|
2003
2037
|
enable_waves=waves,
|
|
2004
2038
|
enable_canals=canals,
|
|
2005
|
-
|
|
2039
|
+
worktree_config=resolved_worktree_config,
|
|
2040
|
+
evidence_config=resolved_evidence_config,
|
|
2006
2041
|
retry_config=retry_config,
|
|
2007
2042
|
)
|
|
2008
2043
|
)
|
|
@@ -2042,7 +2077,8 @@ def agent(
|
|
|
2042
2077
|
force_workflow_id=resolved_workflow_id,
|
|
2043
2078
|
enable_waves=waves,
|
|
2044
2079
|
enable_canals=canals,
|
|
2045
|
-
|
|
2080
|
+
worktree_config=resolved_worktree_config,
|
|
2081
|
+
evidence_config=resolved_evidence_config,
|
|
2046
2082
|
retry_config=retry_config,
|
|
2047
2083
|
)
|
|
2048
2084
|
)
|
|
@@ -2207,6 +2243,32 @@ def _setup_repos(
|
|
|
2207
2243
|
)
|
|
2208
2244
|
|
|
2209
2245
|
|
|
2246
|
+
def _display_dependency_check(deps: list) -> None:
|
|
2247
|
+
"""Display CLI dependency check results as a table."""
|
|
2248
|
+
from rich.table import Table
|
|
2249
|
+
|
|
2250
|
+
table = Table(title="CLI Dependencies", show_header=True, header_style="bold")
|
|
2251
|
+
table.add_column("Tool", style="cyan")
|
|
2252
|
+
table.add_column("Status")
|
|
2253
|
+
table.add_column("Details", style="dim")
|
|
2254
|
+
|
|
2255
|
+
for dep in deps:
|
|
2256
|
+
if dep.found:
|
|
2257
|
+
status = "[green]Found[/green]"
|
|
2258
|
+
details = dep.version or dep.path or ""
|
|
2259
|
+
elif dep.required:
|
|
2260
|
+
status = "[red]Missing (required)[/red]"
|
|
2261
|
+
details = f"Install: {dep.install_hint}"
|
|
2262
|
+
else:
|
|
2263
|
+
status = "[yellow]Missing (optional)[/yellow]"
|
|
2264
|
+
details = f"Install: {dep.install_hint}"
|
|
2265
|
+
table.add_row(dep.name, status, details)
|
|
2266
|
+
|
|
2267
|
+
console.print()
|
|
2268
|
+
console.print(table)
|
|
2269
|
+
console.print()
|
|
2270
|
+
|
|
2271
|
+
|
|
2210
2272
|
def _prompt_install_target() -> str:
|
|
2211
2273
|
"""Prompt the user to choose where to install Claude configs."""
|
|
2212
2274
|
choices = {
|
|
@@ -2324,9 +2386,21 @@ def setup(
|
|
|
2324
2386
|
- user: ~/.claude/ in your home directory (shared across all projects)
|
|
2325
2387
|
"""
|
|
2326
2388
|
from steerdev_agent.setup import ClaudeSetup
|
|
2389
|
+
from steerdev_agent.setup.claude_setup import check_cli_dependencies
|
|
2327
2390
|
|
|
2328
2391
|
target_dir = project_dir or Path.cwd()
|
|
2329
2392
|
|
|
2393
|
+
# ── Check CLI dependencies ──
|
|
2394
|
+
deps = check_cli_dependencies()
|
|
2395
|
+
_display_dependency_check(deps)
|
|
2396
|
+
|
|
2397
|
+
missing_required = [d for d in deps if d.required and not d.found]
|
|
2398
|
+
if missing_required:
|
|
2399
|
+
console.print(
|
|
2400
|
+
"\n[red]Missing required dependencies. Install them before running setup.[/red]"
|
|
2401
|
+
)
|
|
2402
|
+
raise typer.Exit(1)
|
|
2403
|
+
|
|
2330
2404
|
# Prompt for install target if not provided
|
|
2331
2405
|
if install_target is None:
|
|
2332
2406
|
install_target = _prompt_install_target()
|
|
@@ -10,15 +10,45 @@ from steerdev_agent.api.client import DEFAULT_API_ENDPOINT
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class WorktreeConfig(BaseModel):
|
|
13
|
-
"""
|
|
13
|
+
"""Worktree isolation configuration using worktrunk.dev (wt CLI).
|
|
14
14
|
|
|
15
|
-
When enabled
|
|
16
|
-
|
|
15
|
+
When enabled with provider "worktrunk", `wt switch --create <branch>`
|
|
16
|
+
provisions isolated worktrees with lifecycle hooks (env setup, pre-merge checks).
|
|
17
|
+
Legacy provider "claude" uses Claude CLI's --worktree flag directly.
|
|
17
18
|
"""
|
|
18
19
|
|
|
19
20
|
enabled: Annotated[
|
|
20
21
|
bool,
|
|
21
|
-
Field(default=False, description="Enable
|
|
22
|
+
Field(default=False, description="Enable worktree isolation per task/wave"),
|
|
23
|
+
]
|
|
24
|
+
provider: Annotated[
|
|
25
|
+
str,
|
|
26
|
+
Field(
|
|
27
|
+
default="worktrunk",
|
|
28
|
+
description='Worktree provider: "worktrunk" (recommended) or "claude" (legacy --worktree flag)',
|
|
29
|
+
),
|
|
30
|
+
]
|
|
31
|
+
cleanup_on_complete: Annotated[
|
|
32
|
+
bool,
|
|
33
|
+
Field(default=True, description="Remove worktree after successful task completion"),
|
|
34
|
+
]
|
|
35
|
+
cleanup_on_failure: Annotated[
|
|
36
|
+
bool,
|
|
37
|
+
Field(default=False, description="Remove worktree after task failure"),
|
|
38
|
+
]
|
|
39
|
+
copy_gitignored: Annotated[
|
|
40
|
+
list[str],
|
|
41
|
+
Field(
|
|
42
|
+
default_factory=lambda: [".env", ".env.local"],
|
|
43
|
+
description="Gitignored files to copy into new worktrees (via wt step)",
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
pre_merge_checks: Annotated[
|
|
47
|
+
list[str],
|
|
48
|
+
Field(
|
|
49
|
+
default_factory=list,
|
|
50
|
+
description='Commands to run in pre-merge hook (e.g., ["pnpm check", "pnpm typecheck"])',
|
|
51
|
+
),
|
|
22
52
|
]
|
|
23
53
|
|
|
24
54
|
|
|
@@ -216,6 +246,19 @@ class CanalConfig(BaseModel):
|
|
|
216
246
|
]
|
|
217
247
|
|
|
218
248
|
|
|
249
|
+
class EvidenceConfig(BaseModel):
|
|
250
|
+
"""Evidence report configuration.
|
|
251
|
+
|
|
252
|
+
When enabled, the agent submits evidence reports to the platform after
|
|
253
|
+
completing tasks or workflow phases. Reports appear on the project review page.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
enabled: Annotated[
|
|
257
|
+
bool,
|
|
258
|
+
Field(default=False, description="Submit evidence reports after task completion"),
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
|
|
219
262
|
class RetryConfig(BaseModel):
|
|
220
263
|
"""Retry configuration for failed task/session execution."""
|
|
221
264
|
|
|
@@ -295,6 +338,13 @@ class SteerDevConfig(BaseModel):
|
|
|
295
338
|
WorkspaceConfig,
|
|
296
339
|
Field(default_factory=WorkspaceConfig, description="Workspace agent configuration"),
|
|
297
340
|
]
|
|
341
|
+
evidence: Annotated[
|
|
342
|
+
EvidenceConfig,
|
|
343
|
+
Field(
|
|
344
|
+
default_factory=EvidenceConfig,
|
|
345
|
+
description="Evidence report configuration for project review",
|
|
346
|
+
),
|
|
347
|
+
]
|
|
298
348
|
retry: Annotated[
|
|
299
349
|
RetryConfig,
|
|
300
350
|
Field(default_factory=RetryConfig, description="Retry configuration for failed tasks"),
|