steerdev 1.1.61__tar.gz → 1.1.62__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 (157) hide show
  1. steerdev-1.1.62/AGENTS.md +122 -0
  2. {steerdev-1.1.61 → steerdev-1.1.62}/PKG-INFO +27 -4
  3. {steerdev-1.1.61 → steerdev-1.1.62}/README.md +25 -3
  4. {steerdev-1.1.61 → steerdev-1.1.62}/pyproject.toml +2 -1
  5. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/agent_loop.py +5 -4
  6. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/tasks.py +2 -1
  7. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/cli.py +233 -15
  8. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/config/models.py +98 -2
  9. steerdev-1.1.62/src/steerdev_agent/executor/__init__.py +139 -0
  10. steerdev-1.1.62/src/steerdev_agent/executor/acp.py +400 -0
  11. steerdev-1.1.62/src/steerdev_agent/executor/acp_client.py +442 -0
  12. steerdev-1.1.62/src/steerdev_agent/executor/acp_registry.py +154 -0
  13. steerdev-1.1.62/src/steerdev_agent/executor/acp_spec.py +128 -0
  14. steerdev-1.1.62/src/steerdev_agent/executor/acp_stream.py +228 -0
  15. steerdev-1.1.62/src/steerdev_agent/executor/codex.py +137 -0
  16. steerdev-1.1.62/src/steerdev_agent/executor/pi.py +356 -0
  17. steerdev-1.1.62/src/steerdev_agent/executor/pi_stream.py +280 -0
  18. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/runner.py +31 -6
  19. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/steerdev.yaml +1 -1
  20. steerdev-1.1.62/src/steerdev_agent/workspace/tool_detection.py +90 -0
  21. steerdev-1.1.62/tests/fixtures/fake_acp_echo.py +123 -0
  22. steerdev-1.1.62/tests/test_acp_executor.py +293 -0
  23. steerdev-1.1.62/tests/test_acp_registry.py +104 -0
  24. steerdev-1.1.62/tests/test_acp_stream.py +259 -0
  25. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_agent_loop_extended.py +4 -4
  26. steerdev-1.1.62/tests/test_cli_provider.py +209 -0
  27. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_config.py +13 -2
  28. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_config_extended.py +2 -2
  29. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_conflict_mitigation.py +14 -3
  30. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_executor.py +35 -0
  31. steerdev-1.1.62/tests/test_pi_executor.py +332 -0
  32. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_workspace.py +31 -1
  33. steerdev-1.1.61/AGENTS.md +0 -57
  34. steerdev-1.1.61/src/steerdev_agent/executor/__init__.py +0 -73
  35. steerdev-1.1.61/src/steerdev_agent/workspace/tool_detection.py +0 -20
  36. {steerdev-1.1.61 → steerdev-1.1.62}/.github/workflows/pre-commit.yml +0 -0
  37. {steerdev-1.1.61 → steerdev-1.1.62}/.github/workflows/publish.yml +0 -0
  38. {steerdev-1.1.61 → steerdev-1.1.62}/.gitignore +0 -0
  39. {steerdev-1.1.61 → steerdev-1.1.62}/.pre-commit-config.yaml +0 -0
  40. {steerdev-1.1.61 → steerdev-1.1.62}/CLAUDE.md +0 -0
  41. {steerdev-1.1.61 → steerdev-1.1.62}/docs/superpowers/plans/2026-06-05-wave-id-implies-unlimited-max-tasks.md +0 -0
  42. {steerdev-1.1.61 → steerdev-1.1.62}/docs/superpowers/plans/2026-07-15-unified-merge-flow-agent.md +0 -0
  43. {steerdev-1.1.61 → steerdev-1.1.62}/docs/superpowers/specs/2026-06-05-wave-id-implies-unlimited-max-tasks-design.md +0 -0
  44. {steerdev-1.1.61 → steerdev-1.1.62}/scripts/pre-commit-version-bump.sh +0 -0
  45. {steerdev-1.1.61 → steerdev-1.1.62}/snapshots/steerdev-agent-v1/Dockerfile +0 -0
  46. {steerdev-1.1.61 → steerdev-1.1.62}/snapshots/steerdev-agent-v1/start-agent.sh +0 -0
  47. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/__init__.py +0 -0
  48. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/__init__.py +0 -0
  49. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/activity.py +0 -0
  50. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/agents.py +0 -0
  51. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/canals.py +0 -0
  52. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/client.py +0 -0
  53. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/commands.py +0 -0
  54. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/configs.py +0 -0
  55. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/context.py +0 -0
  56. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/events.py +0 -0
  57. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/hooks.py +0 -0
  58. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/implementation_plan.py +0 -0
  59. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/merger.py +0 -0
  60. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/messages.py +0 -0
  61. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/prd.py +0 -0
  62. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/projects.py +0 -0
  63. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/reports.py +0 -0
  64. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/runs.py +0 -0
  65. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/sessions.py +0 -0
  66. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/specs.py +0 -0
  67. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/workflow_runs.py +0 -0
  68. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/api/workflows.py +0 -0
  69. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/config/__init__.py +0 -0
  70. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/config/platform.py +0 -0
  71. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/config/settings.py +0 -0
  72. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/evidence.py +0 -0
  73. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/evidence_assets.py +0 -0
  74. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/executor/base.py +0 -0
  75. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/executor/claude.py +0 -0
  76. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/executor/stream.py +0 -0
  77. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/git/__init__.py +0 -0
  78. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/git/commit.py +0 -0
  79. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/git/pr.py +0 -0
  80. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/git_ops.py +0 -0
  81. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/handlers/__init__.py +0 -0
  82. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/handlers/prd.py +0 -0
  83. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/integration.py +0 -0
  84. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/merge_settings.py +0 -0
  85. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/prompt/__init__.py +0 -0
  86. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/prompt/builder.py +0 -0
  87. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/prompt/templates.py +0 -0
  88. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/prompt/workflow_template.py +0 -0
  89. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/py.typed +0 -0
  90. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/retry.py +0 -0
  91. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/__init__.py +0 -0
  92. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/claude_setup.py +0 -0
  93. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/repo_setup.py +0 -0
  94. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
  95. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
  96. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/settings.json +0 -0
  97. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
  98. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
  99. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
  100. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
  101. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +0 -0
  102. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
  103. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +0 -0
  104. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
  105. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
  106. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +0 -0
  107. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/setup/templates/worktrunk.config.toml +0 -0
  108. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/update_check.py +0 -0
  109. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/version.py +0 -0
  110. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/workflow/__init__.py +0 -0
  111. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/workflow/context.py +0 -0
  112. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/workflow/executor.py +0 -0
  113. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/workflow/memory.py +0 -0
  114. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/workspace/__init__.py +0 -0
  115. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/workspace/preparation.py +0 -0
  116. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/workspace/project_manager.py +0 -0
  117. {steerdev-1.1.61 → steerdev-1.1.62}/src/steerdev_agent/worktree.py +0 -0
  118. {steerdev-1.1.61 → steerdev-1.1.62}/tests/__init__.py +0 -0
  119. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_agent_loop.py +0 -0
  120. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_agent_loop_wave_pr.py +0 -0
  121. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_agents_api.py +0 -0
  122. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_api_client.py +0 -0
  123. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_canal_enroll.py +0 -0
  124. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_claude_executor.py +0 -0
  125. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_claude_setup.py +0 -0
  126. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_client_methods.py +0 -0
  127. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_commands_api.py +0 -0
  128. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_context_search.py +0 -0
  129. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_git_commit.py +0 -0
  130. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_git_ops.py +0 -0
  131. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_git_pr.py +0 -0
  132. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_hooks_post_tool_use.py +0 -0
  133. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_merge_settings.py +0 -0
  134. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_platform_config.py +0 -0
  135. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_preparation.py +0 -0
  136. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_preparation_provenance.py +0 -0
  137. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_projects_api.py +0 -0
  138. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_prompt.py +0 -0
  139. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_reports_client.py +0 -0
  140. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_resolve_executor_config.py +0 -0
  141. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_resolve_max_tasks.py +0 -0
  142. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_retry.py +0 -0
  143. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_runner_boundary_commit.py +0 -0
  144. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_runner_merge_modes.py +0 -0
  145. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_runner_wave_pr.py +0 -0
  146. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_runner_worktrees.py +0 -0
  147. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_stream_parser.py +0 -0
  148. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_tasks.py +0 -0
  149. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_tasks_update_branch.py +0 -0
  150. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_version.py +0 -0
  151. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_wave_scoped_fetch.py +0 -0
  152. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_workflow_context.py +0 -0
  153. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_workflow_memory.py +0 -0
  154. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_workflow_prompt_template.py +0 -0
  155. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_workflow_runs_api.py +0 -0
  156. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_workspace_extended.py +0 -0
  157. {steerdev-1.1.61 → steerdev-1.1.62}/tests/test_worktree.py +0 -0
@@ -0,0 +1,122 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for AI coding agents working in this repository.
4
+
5
+ ## Overview
6
+
7
+ **steerdev-agent** is a Python CLI that orchestrates AI coding agents via subprocess execution, streaming events to the steerdev.com API. It fetches tasks, builds prompts, launches agents (Claude Code and pi are wired; `openai-codex` is a stub), and reports activity.
8
+
9
+ ## Project Structure
10
+
11
+ ```
12
+ src/steerdev_agent/
13
+ ├── cli.py # Typer CLI commands
14
+ ├── runner.py # Main orchestrator
15
+ ├── agent_loop.py # Persistent agent mode
16
+ ├── version.py # Version management
17
+ ├── integration.py # Integration utilities
18
+ ├── api/ # API clients (tasks, sessions, events, workflows, etc.)
19
+ ├── config/ # Pydantic config models and settings
20
+ ├── executor/ # Agent subprocess execution (base, claude, stream)
21
+ ├── workflow/ # Multi-phase workflow execution and memory
22
+ ├── prompt/ # Prompt building and templates
23
+ ├── setup/ # Project setup and templates
24
+ ├── handlers/ # Task/PRD handlers
25
+ └── git/ # Git utilities
26
+ ```
27
+
28
+ ## Development
29
+
30
+ - **Python 3.12+** with **uv** package manager
31
+ - `uv sync` — install deps
32
+ - `uv run pytest` — run tests
33
+ - `uv run ruff check . && uv run ruff format .` — lint and format
34
+ - `uv run basedpyright` — type check
35
+
36
+ ## Conventions
37
+
38
+ - Type hints on all function signatures
39
+ - Pydantic v2 for data models and config
40
+ - Async/await for I/O (HTTP, subprocess)
41
+ - `loguru` for logging
42
+ - Specific exception classes (no bare `except`)
43
+ - Absolute imports (`from steerdev_agent.config.models import ...`)
44
+ - Import order: stdlib, third-party, local
45
+
46
+ ## Key Integration Points
47
+
48
+ - **steerdev.com API**: Bearer auth via `STEERDEV_API_KEY`. Events POSTed to `/api/v1/sessions/{id}/events`.
49
+ - **Executor**: Launches agents as subprocesses with `--output-format stream-json`. Parses newline-delimited JSON events.
50
+ - **Config**: YAML config file + environment variable overrides.
51
+
52
+ ## Environment Variables
53
+
54
+ | Variable | Purpose |
55
+ | --- | --- |
56
+ | `STEERDEV_API_KEY` | API authentication |
57
+ | `STEERDEV_PROJECT_ID` | Default project |
58
+ | `STEERDEV_PROVIDER` | Harness provider: `claude` (default), `pi`, `openai-codex`, `acp:<agent>` |
59
+ | `STEERDEV_ACP_HARNESS` | ACP agent name when the provider is bare `acp` (equivalent to `--acp-agent`) |
60
+ | `CLAUDE_CODE_OAUTH_TOKEN` / `ANTHROPIC_API_KEY` | Claude credentials (sandbox-injected; locally use `claude login` instead) |
61
+ | `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` / `GEMINI_API_KEY` | pi credentials — whichever matches the selected model's provider (sandbox-injected; locally use `pi` + `/login`) |
62
+ | `OPENAI_API_KEY` | Codex credentials for `openai-codex` (sandbox-injected) |
63
+ | Per-harness (e.g. `GEMINI_API_KEY` for `acp:gemini`) | ACP credentials — declared per agent in the registry (`credential_env`) |
64
+
65
+ ## Providers
66
+
67
+ The agent supports multiple harness providers via `--provider` (or `STEERDEV_PROVIDER`):
68
+
69
+ ```bash
70
+ steerdev run --provider claude --project-id abc123 # default (Claude Code)
71
+ steerdev run --provider pi --project-id abc123 # pi.dev coding agent
72
+ steerdev run --provider acp:gemini --project-id abc123 # any ACP harness (Gemini CLI here)
73
+ steerdev run --provider openai-codex --project-id abc123 # stub (not yet wired)
74
+ ```
75
+
76
+ `claude` (Claude Code) and `pi` (the [pi.dev](https://pi.dev) coding agent) are
77
+ wired end-to-end. `pi` is launched headless as `pi --mode json "<prompt>"`, with
78
+ resume via `--session`, model via `--model`, and tool allow/deny lists via
79
+ `--tools`/`--exclude-tools`; see `executor/pi.py` and `executor/pi_stream.py`.
80
+ `openai-codex` is a clearly-stubbed seam (`executor/codex.py`) that fails fast
81
+ with a "not yet supported" message.
82
+
83
+ ### ACP harnesses (`acp:<agent>`)
84
+
85
+ `acp:<agent>` drives **any** harness speaking the
86
+ [Agent Client Protocol](https://agentclientprotocol.com) (JSON-RPC 2.0 over
87
+ stdio) through a single executor — steerdev acts as the ACP *Client*
88
+ (filesystem, terminals, permissions) and the harness is the ACP *Agent*.
89
+ Built-in registry entries (`executor/acp_registry.py`): `acp:gemini`
90
+ (Gemini CLI via `gemini --experimental-acp`), `acp:claude-code` (via
91
+ `npx @zed-industries/claude-code-acp`), `acp:goose` (`goose acp`).
92
+
93
+ Adding a new harness requires **no code** — declare it under `acp.agents` in
94
+ `steerdev.yaml`:
95
+
96
+ ```yaml
97
+ acp:
98
+ # default agent for `--provider acp`
99
+ agent: gemini
100
+ # auto_approve_all (default) | auto_approve_workdir | read_only | escalate
101
+ permission_policy: auto_approve_all
102
+ agents:
103
+ myagent:
104
+ name: myagent
105
+ command: my-acp-agent
106
+ args: ["--serve"]
107
+ credential_env: ["MY_AGENT_API_KEY"]
108
+ ```
109
+
110
+ Key modules: `executor/acp.py` (`ACPExecutor` — spawn, initialize,
111
+ session/new|load, prompt), `executor/acp_client.py` (`SteerdevAcpClient` +
112
+ `AcpPermissionPolicy` — fs/terminal/permission callbacks), `executor/acp_stream.py`
113
+ (ACP `session/update` → canonical `EventType` translation), `executor/acp_spec.py`
114
+ + `executor/acp_registry.py` (declarative launch specs). ACP turns end with a
115
+ `stopReason`, not an exit code — `wait()` synthesizes one (nonzero only on
116
+ refusal/error).
117
+
118
+ Credentials arrive via ENV (the sandbox bootstrap injects them); the agent never
119
+ fetches credentials itself. For providers with a persisted local login (`claude`,
120
+ `pi`, and ACP agents with `requires_local_login`) a missing credential env var
121
+ only warns; for the rest the matching env var must be present or the command
122
+ hard-fails.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerdev
3
- Version: 1.1.61
3
+ Version: 1.1.62
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
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
17
  Classifier: Topic :: Terminals
18
18
  Requires-Python: >=3.12
19
+ Requires-Dist: agent-client-protocol<0.11,>=0.10
19
20
  Requires-Dist: httpx>=0.27.0
20
21
  Requires-Dist: loguru>=0.7.3
21
22
  Requires-Dist: pydantic-settings>=2.0.0
@@ -43,7 +44,7 @@ Task runner for [steerdev.com](https://steerdev.com) — orchestrates AI coding
43
44
 
44
45
  ## What It Does
45
46
 
46
- SteerDev Agent connects your AI coding agent (Claude Code, Codex, Aider) to the steerdev.com platform:
47
+ SteerDev Agent connects your AI coding agent (Claude Code or pi; `openai-codex` is a stub) to the steerdev.com platform:
47
48
 
48
49
  1. **Fetches tasks** from your SteerDev project
49
50
  2. **Builds prompts** with project context, task details, and workflow instructions
@@ -299,7 +300,7 @@ api:
299
300
  project_id_env: STEERDEV_PROJECT_ID
300
301
 
301
302
  executor:
302
- type: claude # claude, codex, aider
303
+ type: claude # claude (wired), pi (wired), openai-codex (stub)
303
304
  permission_mode: default
304
305
  allowed_tools: []
305
306
  disallowed_tools: []
@@ -316,8 +317,30 @@ worktrees:
316
317
  | `STEERDEV_API_KEY` | API key for steerdev.com |
317
318
  | `STEERDEV_PROJECT_ID` | Default project ID |
318
319
  | `STEERDEV_AGENT_NAME` | Agent name for session tracking |
320
+ | `STEERDEV_PROVIDER` | Harness provider: `claude` (default), `pi`, `openai-codex` |
321
+ | `CLAUDE_CODE_OAUTH_TOKEN` / `ANTHROPIC_API_KEY` | Claude credentials (sandbox-injected; locally use `claude login`) |
322
+ | `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` / `GEMINI_API_KEY` | pi credentials — whichever matches the selected model's provider (sandbox-injected; locally use `pi` + `/login`) |
323
+ | `OPENAI_API_KEY` | Codex credentials for `openai-codex` (sandbox-injected) |
319
324
 
320
- > **Note:** CLI agents (Claude Code, Codex, etc.) must be logged in independently before running steerdev. No LLM API keys are needed here.
325
+ > **Note:** Locally, CLI agents like Claude Code and pi can be logged in
326
+ > independently (`claude login`, or `pi` + `/login`) before running steerdev — no
327
+ > LLM API keys are strictly needed. In a sandbox, the bootstrap injects the
328
+ > per-provider credentials above; steerdev never fetches credentials itself.
329
+
330
+ ### Providers
331
+
332
+ Select the harness with `--provider` (or `STEERDEV_PROVIDER`). Defaults to `claude`:
333
+
334
+ ```bash
335
+ steerdev run --provider claude --project-id YOUR_PROJECT_ID # Claude Code
336
+ steerdev run --provider pi --project-id YOUR_PROJECT_ID # pi.dev coding agent
337
+ ```
338
+
339
+ `claude` (Claude Code) and `pi` (the [pi.dev](https://pi.dev) coding agent) are
340
+ wired end-to-end. `pi` runs headless as `pi --mode json "<prompt>"` and streams
341
+ its JSON events back to steerdev. `openai-codex` is a clearly-stubbed seam that
342
+ fails fast with a "not yet supported" message, reserved for a future harness
343
+ protocol.
321
344
 
322
345
  ## Architecture
323
346
 
@@ -6,7 +6,7 @@ Task runner for [steerdev.com](https://steerdev.com) — orchestrates AI coding
6
6
 
7
7
  ## What It Does
8
8
 
9
- SteerDev Agent connects your AI coding agent (Claude Code, Codex, Aider) to the steerdev.com platform:
9
+ SteerDev Agent connects your AI coding agent (Claude Code or pi; `openai-codex` is a stub) to the steerdev.com platform:
10
10
 
11
11
  1. **Fetches tasks** from your SteerDev project
12
12
  2. **Builds prompts** with project context, task details, and workflow instructions
@@ -262,7 +262,7 @@ api:
262
262
  project_id_env: STEERDEV_PROJECT_ID
263
263
 
264
264
  executor:
265
- type: claude # claude, codex, aider
265
+ type: claude # claude (wired), pi (wired), openai-codex (stub)
266
266
  permission_mode: default
267
267
  allowed_tools: []
268
268
  disallowed_tools: []
@@ -279,8 +279,30 @@ worktrees:
279
279
  | `STEERDEV_API_KEY` | API key for steerdev.com |
280
280
  | `STEERDEV_PROJECT_ID` | Default project ID |
281
281
  | `STEERDEV_AGENT_NAME` | Agent name for session tracking |
282
+ | `STEERDEV_PROVIDER` | Harness provider: `claude` (default), `pi`, `openai-codex` |
283
+ | `CLAUDE_CODE_OAUTH_TOKEN` / `ANTHROPIC_API_KEY` | Claude credentials (sandbox-injected; locally use `claude login`) |
284
+ | `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` / `GEMINI_API_KEY` | pi credentials — whichever matches the selected model's provider (sandbox-injected; locally use `pi` + `/login`) |
285
+ | `OPENAI_API_KEY` | Codex credentials for `openai-codex` (sandbox-injected) |
282
286
 
283
- > **Note:** CLI agents (Claude Code, Codex, etc.) must be logged in independently before running steerdev. No LLM API keys are needed here.
287
+ > **Note:** Locally, CLI agents like Claude Code and pi can be logged in
288
+ > independently (`claude login`, or `pi` + `/login`) before running steerdev — no
289
+ > LLM API keys are strictly needed. In a sandbox, the bootstrap injects the
290
+ > per-provider credentials above; steerdev never fetches credentials itself.
291
+
292
+ ### Providers
293
+
294
+ Select the harness with `--provider` (or `STEERDEV_PROVIDER`). Defaults to `claude`:
295
+
296
+ ```bash
297
+ steerdev run --provider claude --project-id YOUR_PROJECT_ID # Claude Code
298
+ steerdev run --provider pi --project-id YOUR_PROJECT_ID # pi.dev coding agent
299
+ ```
300
+
301
+ `claude` (Claude Code) and `pi` (the [pi.dev](https://pi.dev) coding agent) are
302
+ wired end-to-end. `pi` runs headless as `pi --mode json "<prompt>"` and streams
303
+ its JSON events back to steerdev. `openai-codex` is a clearly-stubbed seam that
304
+ fails fast with a "not yet supported" message, reserved for a future harness
305
+ protocol.
284
306
 
285
307
  ## Architecture
286
308
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "steerdev"
3
- version = "1.1.61"
3
+ version = "1.1.62"
4
4
  description = "Backend task runner for steerdev.com - orchestrates CLI coding agents with activity reporting"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -28,6 +28,7 @@ dependencies = [
28
28
  "pyyaml>=6.0.0",
29
29
  "python-dotenv>=1.2.1",
30
30
  "tenacity>=9.0.0",
31
+ "agent-client-protocol>=0.10,<0.11",
31
32
  ]
32
33
 
33
34
  [project.optional-dependencies]
@@ -46,6 +46,7 @@ from steerdev_agent.config.models import (
46
46
  WorktreeConfig,
47
47
  )
48
48
  from steerdev_agent.executor import ExecutorFactory
49
+ from steerdev_agent.executor.acp_registry import application_for_agent_type
49
50
  from steerdev_agent.executor.base import EventType
50
51
  from steerdev_agent.executor.claude import ClaudeExecutorError
51
52
 
@@ -747,7 +748,7 @@ class AgentLoop(CommandExecutor):
747
748
  json={
748
749
  "name": self.agent_name,
749
750
  "project_id": self.project_id,
750
- "application": self.agent_type,
751
+ "application": application_for_agent_type(self.agent_type),
751
752
  "hostname": socket.gethostname(),
752
753
  },
753
754
  )
@@ -917,7 +918,7 @@ class AgentLoop(CommandExecutor):
917
918
  json={
918
919
  "name": self.agent_name,
919
920
  "project_id": self.project_id,
920
- "application": self.agent_type,
921
+ "application": application_for_agent_type(self.agent_type),
921
922
  "hostname": socket.gethostname(),
922
923
  },
923
924
  )
@@ -1152,7 +1153,7 @@ class WorkspaceAgentLoop(CommandExecutor):
1152
1153
  try:
1153
1154
  result = await agents_client.register_workspace(
1154
1155
  name=self.agent_name,
1155
- application=self.agent_type,
1156
+ application=application_for_agent_type(self.agent_type),
1156
1157
  hostname=socket.gethostname(),
1157
1158
  workspace_path=str(self.workspace_path),
1158
1159
  capabilities=detect_installed_tools(),
@@ -1355,7 +1356,7 @@ class WorkspaceAgentLoop(CommandExecutor):
1355
1356
  try:
1356
1357
  await agents_client.register_workspace(
1357
1358
  name=self.agent_name,
1358
- application=self.agent_type,
1359
+ application=application_for_agent_type(self.agent_type),
1359
1360
  hostname=socket.gethostname(),
1360
1361
  workspace_path=str(self.workspace_path),
1361
1362
  capabilities=detect_installed_tools(),
@@ -468,11 +468,12 @@ class TasksClient(SteerDevClient):
468
468
  payload["actual_files"] = actual_files
469
469
  if predicted_files is not None:
470
470
  payload["predicted_files"] = predicted_files
471
- payload["source"] = "agent_reported"
472
471
 
473
472
  if not payload:
474
473
  return False
475
474
 
475
+ payload["source"] = "agent_reported"
476
+
476
477
  response = self.patch(f"/tasks/{task_id}/files", json=payload)
477
478
  if response.status_code not in (200, 204):
478
479
  logger.warning(f"Failed to report files for task {task_id}: {response.status_code}")
@@ -13,7 +13,11 @@ from loguru import logger
13
13
  from rich.console import Console
14
14
  from rich.panel import Panel
15
15
 
16
- from steerdev_agent.config.models import SteerDevConfig
16
+ from steerdev_agent.config.models import (
17
+ SUPPORTED_PROVIDERS,
18
+ SteerDevConfig,
19
+ is_supported_provider,
20
+ )
17
21
  from steerdev_agent.update_check import check_for_update
18
22
  from steerdev_agent.version import get_version
19
23
 
@@ -22,6 +26,133 @@ console = Console()
22
26
  # Load environment variables from .env file in current working directory
23
27
  load_dotenv(Path.cwd() / ".env", override=True)
24
28
 
29
+ # ============================================================================
30
+ # Provider (harness) resolution helpers
31
+ # ============================================================================
32
+ # Candidate credential env vars per provider, in priority order. The sandbox
33
+ # bootstrap injects these; the agent never fetches credentials itself. For
34
+ # providers that support a persisted local login (claude, pi), a missing env
35
+ # var only warns; for the rest it hard-fails.
36
+ PROVIDER_CREDENTIAL_ENV: dict[str, tuple[str, ...]] = {
37
+ "claude": ("CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"),
38
+ "pi": ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY", "GEMINI_API_KEY"),
39
+ "openai-codex": ("OPENAI_API_KEY",),
40
+ }
41
+
42
+ # Providers whose CLI can fall back to a persisted local login session, so a
43
+ # missing credential env var is a warning rather than a hard failure.
44
+ LOCAL_LOGIN_PROVIDERS: dict[str, str] = {
45
+ "claude": "claude login",
46
+ "pi": "pi (then /login)",
47
+ }
48
+
49
+
50
+ def _resolve_provider(cli_value: str | None, config: SteerDevConfig) -> str:
51
+ """Resolve the harness provider with precedence flag > env > config > default.
52
+
53
+ Args:
54
+ cli_value: Value of the ``--provider`` option (None if not passed).
55
+ config: Loaded SteerDev configuration.
56
+
57
+ Returns:
58
+ The resolved provider identifier (defaults to "claude").
59
+ """
60
+ if cli_value:
61
+ return cli_value
62
+ env_value = os.environ.get("STEERDEV_PROVIDER")
63
+ if env_value:
64
+ return env_value
65
+ if config.executor.type:
66
+ return config.executor.type
67
+ return "claude"
68
+
69
+
70
+ def _resolve_acp_provider(provider: str, acp_agent: str | None, config: SteerDevConfig) -> str:
71
+ """Normalize a bare ``acp`` provider to the full ``acp:<agent>`` form.
72
+
73
+ Args:
74
+ provider: The resolved provider identifier.
75
+ acp_agent: Value of the ``--acp-agent`` option (None if not passed).
76
+ config: Loaded SteerDev configuration.
77
+
78
+ Returns:
79
+ The provider with the ACP agent name attached when applicable.
80
+
81
+ Raises:
82
+ typer.Exit: If the ACP provider has no agent name to resolve.
83
+ """
84
+ if provider != "acp":
85
+ return provider
86
+ agent_name = acp_agent or config.acp.agent
87
+ if not agent_name:
88
+ console.print(
89
+ "[red]Error: provider 'acp' requires an agent name. Use "
90
+ "--provider acp:<agent> (e.g. acp:gemini), pass --acp-agent, or "
91
+ "set acp.agent in the config.[/red]"
92
+ )
93
+ raise typer.Exit(1)
94
+ return f"acp:{agent_name}"
95
+
96
+
97
+ def _check_provider_credentials(provider: str, config: SteerDevConfig | None = None) -> None:
98
+ """Validate that credentials for a provider are present in the environment.
99
+
100
+ Providers that support a persisted local login (``claude``, ``pi`` — see
101
+ ``LOCAL_LOGIN_PROVIDERS``; ACP harnesses via ``requires_local_login``) only
102
+ warn when no candidate env var is set, so a local login session keeps
103
+ working. Other providers (e.g. ``openai-codex``) hard-fail.
104
+
105
+ Args:
106
+ provider: The resolved provider identifier.
107
+ config: Loaded configuration (for ACP registry overrides).
108
+
109
+ Raises:
110
+ typer.Exit: If a provider without a local-login fallback has no
111
+ credentials in the environment.
112
+ """
113
+ candidates = PROVIDER_CREDENTIAL_ENV.get(provider, ())
114
+ local_login_hint = LOCAL_LOGIN_PROVIDERS.get(provider)
115
+
116
+ # ACP providers carry their credential contract in the harness spec.
117
+ if provider.startswith("acp:"):
118
+ from steerdev_agent.executor.acp_registry import (
119
+ acp_harness_from_provider,
120
+ resolve_acp_spec,
121
+ )
122
+
123
+ harness = acp_harness_from_provider(provider)
124
+ overrides = config.acp.agents if config else None
125
+ try:
126
+ spec = resolve_acp_spec(harness or "", overrides)
127
+ except ValueError as e:
128
+ console.print(f"[red]Error: {e}[/red]")
129
+ raise typer.Exit(1) from e
130
+ candidates = tuple(spec.credential_env)
131
+ if spec.requires_local_login:
132
+ local_login_hint = spec.local_login_hint or spec.command
133
+
134
+ has_credential = any(os.environ.get(var) for var in candidates)
135
+ if has_credential:
136
+ return
137
+
138
+ if local_login_hint is not None:
139
+ vars_list = " or ".join(candidates) if candidates else "(none configured)"
140
+ console.print(
141
+ f"[yellow]Warning: no credentials for '{provider}' in the environment "
142
+ f"({vars_list}). Relying on a local `{local_login_hint}` "
143
+ f"session.[/yellow]"
144
+ )
145
+ return
146
+
147
+ vars_list = " or ".join(candidates) if candidates else "(none configured)"
148
+ console.print(
149
+ f"[red]Error: provider '{provider}' requires credentials in the "
150
+ f"environment. Set {vars_list} (the sandbox bootstrap normally injects "
151
+ f"these).[/red]"
152
+ )
153
+ raise typer.Exit(1)
154
+
155
+
25
156
  app = typer.Typer(
26
157
  name="steerdev",
27
158
  help="Backend task runner for steerdev.com - orchestrates CLI coding agents",
@@ -1614,18 +1745,6 @@ def run(
1614
1745
  envvar="STEERDEV_AGENT_ID",
1615
1746
  ),
1616
1747
  ] = None,
1617
- provider: Annotated[
1618
- str | None,
1619
- typer.Option(
1620
- "--provider",
1621
- help=(
1622
- "AI provider / executor type to run tasks with (e.g. claude)."
1623
- " Overrides steerdev.yaml executor.type."
1624
- " Reads from STEERDEV_PROVIDER env var."
1625
- ),
1626
- envvar="STEERDEV_PROVIDER",
1627
- ),
1628
- ] = None,
1629
1748
  model: Annotated[
1630
1749
  str | None,
1631
1750
  typer.Option(
@@ -1769,6 +1888,22 @@ def run(
1769
1888
  ),
1770
1889
  ),
1771
1890
  ] = None,
1891
+ provider: Annotated[
1892
+ str | None,
1893
+ typer.Option(
1894
+ "--provider",
1895
+ help="Harness provider: claude | pi | openai-codex | acp:<agent> (default: claude)",
1896
+ envvar="STEERDEV_PROVIDER",
1897
+ ),
1898
+ ] = None,
1899
+ acp_agent: Annotated[
1900
+ str | None,
1901
+ typer.Option(
1902
+ "--acp-agent",
1903
+ help="ACP agent name when --provider is 'acp' (e.g. gemini, claude-code, goose)",
1904
+ envvar="STEERDEV_ACP_HARNESS",
1905
+ ),
1906
+ ] = None,
1772
1907
  ) -> None:
1773
1908
  """Run the agent for a project!
1774
1909
 
@@ -1855,6 +1990,23 @@ def run(
1855
1990
  f"canals={merge_flags.enable_canals} ({merge_flags.canals_source})"
1856
1991
  )
1857
1992
 
1993
+ # Resolve provider: CLI flag > STEERDEV_PROVIDER env > config > default.
1994
+ resolved_provider = _resolve_provider(provider, config)
1995
+ resolved_provider = _resolve_acp_provider(resolved_provider, acp_agent, config)
1996
+ if not is_supported_provider(resolved_provider):
1997
+ supported = ", ".join((*SUPPORTED_PROVIDERS, "acp:<agent>"))
1998
+ console.print(
1999
+ f"[red]Error: unsupported provider '{resolved_provider}'. Supported: {supported}.[/red]"
2000
+ )
2001
+ raise typer.Exit(1)
2002
+ resolved_executor_config = config.executor.model_copy(
2003
+ update={
2004
+ "type": resolved_provider,
2005
+ "acp_agents": config.acp.agents,
2006
+ "acp_permission_policy": config.acp.permission_policy,
2007
+ }
2008
+ )
2009
+
1858
2010
  worktree_status = (
1859
2011
  f"enabled ({resolved_worktree_config.provider})"
1860
2012
  if resolved_worktree_config.enabled
@@ -1879,6 +2031,7 @@ def run(
1879
2031
  f"[bold blue]SteerDev Agent[/bold blue]\n"
1880
2032
  f"Project ID: {resolved_project_id}\n"
1881
2033
  f"Agent Name: {agent_name_display}\n"
2034
+ f"Provider: {resolved_provider}\n"
1882
2035
  f"Working Directory: {working_dir or 'current'}\n"
1883
2036
  f"Provider: {resolved_executor_config.type}\n"
1884
2037
  f"Model: {resolved_model or 'default'}\n"
@@ -1912,12 +2065,28 @@ def run(
1912
2065
  if default_branch is not None:
1913
2066
  resolved_branch_config.default_branch = default_branch
1914
2067
 
2068
+ # Preflight provider checks (skip in dry-run so command preview works offline).
2069
+ if not dry_run:
2070
+ from steerdev_agent.workspace.tool_detection import (
2071
+ cli_for_provider,
2072
+ is_provider_cli_available,
2073
+ )
2074
+
2075
+ _check_provider_credentials(resolved_provider, config)
2076
+ if not is_provider_cli_available(resolved_provider, config.acp.agents):
2077
+ console.print(
2078
+ f"[red]Error: CLI '{cli_for_provider(resolved_provider, config.acp.agents)}' "
2079
+ f"for provider '{resolved_provider}' not found in PATH.[/red]"
2080
+ )
2081
+ raise typer.Exit(1)
2082
+
1915
2083
  try:
1916
2084
  result = asyncio.run(
1917
2085
  run_agent(
1918
2086
  project_id=resolved_project_id,
1919
2087
  working_directory=str(working_dir) if working_dir else None,
1920
2088
  api_key=resolved_api_key,
2089
+ agent_type=resolved_provider,
1921
2090
  agent_name=agent_name,
1922
2091
  model=resolved_model,
1923
2092
  max_turns=resolved_max_turns,
@@ -2106,6 +2275,22 @@ def agent(
2106
2275
  help="Default branch for sync/rebase/PR targets (default: from config or 'main')",
2107
2276
  ),
2108
2277
  ] = None,
2278
+ provider: Annotated[
2279
+ str | None,
2280
+ typer.Option(
2281
+ "--provider",
2282
+ help="Harness provider: claude | pi | openai-codex | acp:<agent> (default: claude)",
2283
+ envvar="STEERDEV_PROVIDER",
2284
+ ),
2285
+ ] = None,
2286
+ acp_agent: Annotated[
2287
+ str | None,
2288
+ typer.Option(
2289
+ "--acp-agent",
2290
+ help="ACP agent name when --provider is 'acp' (e.g. gemini, claude-code, goose)",
2291
+ envvar="STEERDEV_ACP_HARNESS",
2292
+ ),
2293
+ ] = None,
2109
2294
  ) -> None:
2110
2295
  """Run the agent in persistent mode.
2111
2296
 
@@ -2188,6 +2373,37 @@ def agent(
2188
2373
  if default_branch is not None:
2189
2374
  resolved_branch_config.default_branch = default_branch
2190
2375
 
2376
+ # Resolve provider: CLI flag > STEERDEV_PROVIDER env > config > default.
2377
+ resolved_provider = _resolve_provider(provider, config)
2378
+ resolved_provider = _resolve_acp_provider(resolved_provider, acp_agent, config)
2379
+ if not is_supported_provider(resolved_provider):
2380
+ supported = ", ".join((*SUPPORTED_PROVIDERS, "acp:<agent>"))
2381
+ console.print(
2382
+ f"[red]Error: unsupported provider '{resolved_provider}'. Supported: {supported}.[/red]"
2383
+ )
2384
+ raise typer.Exit(1)
2385
+ resolved_executor_config = config.executor.model_copy(
2386
+ update={
2387
+ "type": resolved_provider,
2388
+ "acp_agents": config.acp.agents,
2389
+ "acp_permission_policy": config.acp.permission_policy,
2390
+ }
2391
+ )
2392
+
2393
+ # Preflight provider checks (agent mode always launches real subprocesses).
2394
+ from steerdev_agent.workspace.tool_detection import (
2395
+ cli_for_provider,
2396
+ is_provider_cli_available,
2397
+ )
2398
+
2399
+ _check_provider_credentials(resolved_provider, config)
2400
+ if not is_provider_cli_available(resolved_provider, config.acp.agents):
2401
+ console.print(
2402
+ f"[red]Error: CLI '{cli_for_provider(resolved_provider, config.acp.agents)}' "
2403
+ f"for provider '{resolved_provider}' not found in PATH.[/red]"
2404
+ )
2405
+ raise typer.Exit(1)
2406
+
2191
2407
  # Resolve workspace path: CLI > config
2192
2408
  resolved_workspace = workspace or config.workspace.workspace_dir
2193
2409
 
@@ -2217,12 +2433,13 @@ def agent(
2217
2433
  asyncio.run(
2218
2434
  run_workspace_agent_loop(
2219
2435
  workspace_path=str(resolved_workspace),
2436
+ agent_type=resolved_provider,
2220
2437
  agent_name=agent_name,
2221
2438
  api_key=resolved_api_key,
2222
2439
  model=resolved_model,
2223
2440
  max_turns=resolved_max_turns,
2224
2441
  agent_loop_config=agent_loop_config,
2225
- executor_config=config.executor,
2442
+ executor_config=resolved_executor_config,
2226
2443
  workspace_config=config.workspace,
2227
2444
  force_workflow_id=workflow_id,
2228
2445
  enable_waves=merge_flags.enable_waves,
@@ -2273,13 +2490,14 @@ def agent(
2273
2490
  asyncio.run(
2274
2491
  run_agent_loop(
2275
2492
  project_id=resolved_project_id,
2493
+ agent_type=resolved_provider,
2276
2494
  agent_name=agent_name,
2277
2495
  working_directory=str(working_dir) if working_dir else None,
2278
2496
  api_key=resolved_api_key,
2279
2497
  model=resolved_model,
2280
2498
  max_turns=resolved_max_turns,
2281
2499
  agent_loop_config=agent_loop_config,
2282
- executor_config=config.executor,
2500
+ executor_config=resolved_executor_config,
2283
2501
  force_workflow_id=resolved_workflow_id,
2284
2502
  enable_waves=merge_flags.enable_waves,
2285
2503
  enable_canals=merge_flags.enable_canals,