slack-claude-code 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. slack_claude_code-0.1.1/PKG-INFO +163 -0
  2. slack_claude_code-0.1.1/README.md +142 -0
  3. slack_claude_code-0.1.1/pyproject.toml +29 -0
  4. slack_claude_code-0.1.1/src/__init__.py +1 -0
  5. slack_claude_code-0.1.1/src/agents/__init__.py +10 -0
  6. slack_claude_code-0.1.1/src/agents/orchestrator.py +316 -0
  7. slack_claude_code-0.1.1/src/agents/roles.py +164 -0
  8. slack_claude_code-0.1.1/src/app.py +696 -0
  9. slack_claude_code-0.1.1/src/approval/__init__.py +1 -0
  10. slack_claude_code-0.1.1/src/approval/handler.py +304 -0
  11. slack_claude_code-0.1.1/src/approval/plan_manager.py +228 -0
  12. slack_claude_code-0.1.1/src/approval/slack_ui.py +312 -0
  13. slack_claude_code-0.1.1/src/claude/__init__.py +2 -0
  14. slack_claude_code-0.1.1/src/claude/executor.py +291 -0
  15. slack_claude_code-0.1.1/src/claude/streaming.py +349 -0
  16. slack_claude_code-0.1.1/src/claude/subprocess_executor.py +516 -0
  17. slack_claude_code-0.1.1/src/config.py +180 -0
  18. slack_claude_code-0.1.1/src/database/__init__.py +1 -0
  19. slack_claude_code-0.1.1/src/database/migrations.py +185 -0
  20. slack_claude_code-0.1.1/src/database/models.py +262 -0
  21. slack_claude_code-0.1.1/src/database/repository.py +762 -0
  22. slack_claude_code-0.1.1/src/exceptions.py +218 -0
  23. slack_claude_code-0.1.1/src/git/__init__.py +1 -0
  24. slack_claude_code-0.1.1/src/git/models.py +62 -0
  25. slack_claude_code-0.1.1/src/git/service.py +353 -0
  26. slack_claude_code-0.1.1/src/handlers/__init__.py +55 -0
  27. slack_claude_code-0.1.1/src/handlers/actions.py +1049 -0
  28. slack_claude_code-0.1.1/src/handlers/agents.py +270 -0
  29. slack_claude_code-0.1.1/src/handlers/base.py +163 -0
  30. slack_claude_code-0.1.1/src/handlers/basic.py +137 -0
  31. slack_claude_code-0.1.1/src/handlers/claude_cli.py +372 -0
  32. slack_claude_code-0.1.1/src/handlers/git.py +317 -0
  33. slack_claude_code-0.1.1/src/handlers/mode.py +124 -0
  34. slack_claude_code-0.1.1/src/handlers/notifications.py +195 -0
  35. slack_claude_code-0.1.1/src/handlers/parallel.py +71 -0
  36. slack_claude_code-0.1.1/src/handlers/pty.py +87 -0
  37. slack_claude_code-0.1.1/src/handlers/queue.py +278 -0
  38. slack_claude_code-0.1.1/src/handlers/session_management.py +49 -0
  39. slack_claude_code-0.1.1/src/hooks/__init__.py +4 -0
  40. slack_claude_code-0.1.1/src/hooks/registry.py +201 -0
  41. slack_claude_code-0.1.1/src/hooks/types.py +61 -0
  42. slack_claude_code-0.1.1/src/pty/__init__.py +1 -0
  43. slack_claude_code-0.1.1/src/pty/parser.py +273 -0
  44. slack_claude_code-0.1.1/src/pty/pool.py +287 -0
  45. slack_claude_code-0.1.1/src/pty/process.py +210 -0
  46. slack_claude_code-0.1.1/src/pty/session.py +317 -0
  47. slack_claude_code-0.1.1/src/pty/types.py +66 -0
  48. slack_claude_code-0.1.1/src/question/__init__.py +1 -0
  49. slack_claude_code-0.1.1/src/question/manager.py +436 -0
  50. slack_claude_code-0.1.1/src/question/slack_ui.py +328 -0
  51. slack_claude_code-0.1.1/src/tasks/__init__.py +1 -0
  52. slack_claude_code-0.1.1/src/tasks/manager.py +351 -0
  53. slack_claude_code-0.1.1/src/utils/__init__.py +1 -0
  54. slack_claude_code-0.1.1/src/utils/detail_cache.py +89 -0
  55. slack_claude_code-0.1.1/src/utils/file_downloader.py +140 -0
  56. slack_claude_code-0.1.1/src/utils/formatters/__init__.py +1 -0
  57. slack_claude_code-0.1.1/src/utils/formatters/base.py +180 -0
  58. slack_claude_code-0.1.1/src/utils/formatters/command.py +148 -0
  59. slack_claude_code-0.1.1/src/utils/formatters/directory.py +64 -0
  60. slack_claude_code-0.1.1/src/utils/formatters/job.py +184 -0
  61. slack_claude_code-0.1.1/src/utils/formatters/markdown.py +85 -0
  62. slack_claude_code-0.1.1/src/utils/formatters/plan.py +183 -0
  63. slack_claude_code-0.1.1/src/utils/formatters/queue.py +103 -0
  64. slack_claude_code-0.1.1/src/utils/formatters/session.py +137 -0
  65. slack_claude_code-0.1.1/src/utils/formatters/streaming.py +85 -0
  66. slack_claude_code-0.1.1/src/utils/formatters/tool_blocks.py +343 -0
  67. slack_claude_code-0.1.1/src/utils/formatting.py +148 -0
  68. slack_claude_code-0.1.1/src/utils/slack_helpers.py +330 -0
  69. slack_claude_code-0.1.1/src/utils/streaming.py +223 -0
  70. slack_claude_code-0.1.1/src/utils/validators.py +36 -0
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: slack-claude-code
3
+ Version: 0.1.1
4
+ Summary: Slack app for running Claude Code CLI commands
5
+ Author: Dan
6
+ Requires-Python: >=3.10,<4.0
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
14
+ Requires-Dist: aiohttp (>=3.13.2,<4.0.0)
15
+ Requires-Dist: aiosqlite (>=0.21.0,<0.22.0)
16
+ Requires-Dist: pexpect (>=4.9.0,<5.0.0)
17
+ Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
18
+ Requires-Dist: slack-bolt (>=1.27.0,<2.0.0)
19
+ Description-Content-Type: text/markdown
20
+
21
+ <p align="center">
22
+ <img src="assets/repo_logo.png" alt="Slack Claude Code Bot" width="1000">
23
+ </p>
24
+
25
+
26
+ **Claude Code, but in Slack.** Access Claude Code remotely from any device, or use it full-time for a better UI experience.
27
+
28
+ ## Why Slack?
29
+
30
+ | Feature | Terminal | Slack |
31
+ |---------|----------|-------|
32
+ | **Code blocks** | Plain text | Syntax-highlighted with copy button |
33
+ | **Long output** | Scrolls off screen | "View Details" modal |
34
+ | **Permissions** | Y/n prompts | Approve/Deny buttons |
35
+ | **Parallel work** | Multiple terminals | Threads with isolated sessions |
36
+ | **File sharing** | `cat` or copy-paste | Drag & drop with preview |
37
+ | **Notifications** | Watch the terminal | Alerts when tasks complete |
38
+
39
+ All Claude Code commands work the same way: `/clear`, `/compact`, `/model`, `/mode`, `/add-dir`, `/review`, plus filesystem and git commands.
40
+
41
+ ## Commands
42
+
43
+ | Category | Command | Description |
44
+ |----------|---------|-------------|
45
+ | CLI | `/init` | Initialize Claude project configuration |
46
+ | CLI | `/memory` | View/edit Claude's memory and context |
47
+ | CLI | `/review` | Review code changes with Claude |
48
+ | CLI | `/doctor` | Diagnose Claude Code installation issues |
49
+ | CLI | `/stats` | Show session statistics |
50
+ | CLI | `/context` | Display current context information |
51
+ | CLI | `/todos` | List and manage todos |
52
+ | Session | `/clear`, `/compact` | Clear session or compact context |
53
+ | Session | `/cost` | Show session cost |
54
+ | Session | `/pty`, `/sessions`, `/session-cleanup` | PTY session management |
55
+ | Navigation | `/ls`, `/cd`, `/pwd`, `/add-dir` | Directory navigation |
56
+ | Git | `/status`, `/diff`, `/commit`, `/branch` | Git operations |
57
+ | Config | `/model`, `/mode`, `/permissions`, `/notifications` | Configuration |
58
+ | Queue | `/q <cmd>`, `/qv`, `/qc`, `/qr <id>` | Command queue management |
59
+ | Jobs | `/st`, `/cc`, `/esc` | Job control |
60
+ | Multi-Agent | `/task`, `/tasks`, `/task-cancel` | Multi-agent task management |
61
+
62
+ ## Installation
63
+
64
+ ### Prerequisites
65
+ - Python 3.10+
66
+ - [Claude Code CLI](https://github.com/anthropics/claude-code) installed and authenticated
67
+
68
+ ### 1. Install dependencies
69
+ ```bash
70
+ cd slack-claude-code
71
+ poetry install
72
+ ```
73
+
74
+ ### 2. Create Slack App
75
+ Go to https://api.slack.com/apps → "Create New App" → "From scratch"
76
+
77
+ **Socket Mode**: Enable and create an app-level token with `connections:write` scope (save the `xapp-` token)
78
+
79
+ **Bot Token Scopes** (OAuth & Permissions):
80
+ - `chat:write`, `commands`, `channels:history`, `app_mentions:read`, `files:write`
81
+
82
+ **Event Subscriptions**: Enable and add `message.channels`, `app_mention`
83
+
84
+ **App Icon**: In "Basic Information" → "Display Information", upload `assets/claude_logo.png` from this repo as the app icon
85
+
86
+ **Slash Commands** (optional): Create commands like `/clear`, `/model`, `/ls`, `/cd`, `/status`, `/diff`, etc.
87
+
88
+ ### 3. Configure and run
89
+ ```bash
90
+ cp .env.example .env
91
+ # Add your tokens: SLACK_BOT_TOKEN, SLACK_APP_TOKEN, SLACK_SIGNING_SECRET
92
+ poetry run python run.py
93
+ ```
94
+
95
+ ## Usage
96
+
97
+ Type messages in any channel where the bot is present. Each Slack thread maintains an independent Claude session with its own working directory and context.
98
+
99
+ ### Key Features
100
+
101
+ - **Threads = Sessions**: Each thread has isolated context; `/clear` only affects that thread
102
+ - **File Uploads**: Drag & drop files—Claude sees them instantly (code, images, PDFs)
103
+ - **Smart Context**: Frequently-used files are automatically included in prompts
104
+ - **Streaming**: Watch Claude's responses as they're generated
105
+
106
+ ### Plan Mode
107
+
108
+ ```
109
+ /mode plan
110
+ ```
111
+
112
+ Claude creates a detailed plan before execution, shown with Approve/Reject buttons. Ideal for complex implementations where you want to review the approach first.
113
+
114
+
115
+ ## Configuration
116
+
117
+ Key environment variables (see `.env.example` for full list):
118
+
119
+ ```bash
120
+ # Required
121
+ SLACK_BOT_TOKEN=xoxb-...
122
+ SLACK_APP_TOKEN=xapp-...
123
+ SLACK_SIGNING_SECRET=...
124
+
125
+ # Optional
126
+ DEFAULT_WORKING_DIR=/path/to/projects
127
+ COMMAND_TIMEOUT=300 # 5 min default
128
+ CLAUDE_PERMISSION_MODE=approve-all # or: prompt, deny
129
+ AUTO_APPROVE_TOOLS=Read,Glob,Grep,LSP
130
+ ```
131
+
132
+ ## Architecture
133
+
134
+ ```
135
+ src/
136
+ ├── app.py # Main entry point
137
+ ├── config.py # Configuration
138
+ ├── database/ # SQLite persistence (models, migrations, repository)
139
+ ├── claude/ # Claude CLI integration (executor, streaming)
140
+ ├── pty/ # PTY session management (session, pool, parser)
141
+ ├── handlers/ # Slack command handlers
142
+ ├── agents/ # Multi-agent orchestration (planner→worker→evaluator)
143
+ ├── approval/ # Permission & plan approval handling
144
+ ├── git/ # Git operations (status, diff, commit, branch)
145
+ ├── hooks/ # Event hook system
146
+ ├── question/ # AskUserQuestion tool support
147
+ ├── tasks/ # Background task management
148
+ └── utils/ # Formatters, helpers, validators
149
+ ```
150
+
151
+ ## Troubleshooting
152
+
153
+ | Problem | Solution |
154
+ |---------|----------|
155
+ | Configuration errors on startup | Check `.env` has all required tokens |
156
+ | Commands not appearing | Verify slash commands in Slack app settings |
157
+ | Timeouts | Increase `COMMAND_TIMEOUT` |
158
+ | PTY session errors | Use `/pty` → "Restart Session" |
159
+
160
+ ## License
161
+
162
+ MIT
163
+
@@ -0,0 +1,142 @@
1
+ <p align="center">
2
+ <img src="assets/repo_logo.png" alt="Slack Claude Code Bot" width="1000">
3
+ </p>
4
+
5
+
6
+ **Claude Code, but in Slack.** Access Claude Code remotely from any device, or use it full-time for a better UI experience.
7
+
8
+ ## Why Slack?
9
+
10
+ | Feature | Terminal | Slack |
11
+ |---------|----------|-------|
12
+ | **Code blocks** | Plain text | Syntax-highlighted with copy button |
13
+ | **Long output** | Scrolls off screen | "View Details" modal |
14
+ | **Permissions** | Y/n prompts | Approve/Deny buttons |
15
+ | **Parallel work** | Multiple terminals | Threads with isolated sessions |
16
+ | **File sharing** | `cat` or copy-paste | Drag & drop with preview |
17
+ | **Notifications** | Watch the terminal | Alerts when tasks complete |
18
+
19
+ All Claude Code commands work the same way: `/clear`, `/compact`, `/model`, `/mode`, `/add-dir`, `/review`, plus filesystem and git commands.
20
+
21
+ ## Commands
22
+
23
+ | Category | Command | Description |
24
+ |----------|---------|-------------|
25
+ | CLI | `/init` | Initialize Claude project configuration |
26
+ | CLI | `/memory` | View/edit Claude's memory and context |
27
+ | CLI | `/review` | Review code changes with Claude |
28
+ | CLI | `/doctor` | Diagnose Claude Code installation issues |
29
+ | CLI | `/stats` | Show session statistics |
30
+ | CLI | `/context` | Display current context information |
31
+ | CLI | `/todos` | List and manage todos |
32
+ | Session | `/clear`, `/compact` | Clear session or compact context |
33
+ | Session | `/cost` | Show session cost |
34
+ | Session | `/pty`, `/sessions`, `/session-cleanup` | PTY session management |
35
+ | Navigation | `/ls`, `/cd`, `/pwd`, `/add-dir` | Directory navigation |
36
+ | Git | `/status`, `/diff`, `/commit`, `/branch` | Git operations |
37
+ | Config | `/model`, `/mode`, `/permissions`, `/notifications` | Configuration |
38
+ | Queue | `/q <cmd>`, `/qv`, `/qc`, `/qr <id>` | Command queue management |
39
+ | Jobs | `/st`, `/cc`, `/esc` | Job control |
40
+ | Multi-Agent | `/task`, `/tasks`, `/task-cancel` | Multi-agent task management |
41
+
42
+ ## Installation
43
+
44
+ ### Prerequisites
45
+ - Python 3.10+
46
+ - [Claude Code CLI](https://github.com/anthropics/claude-code) installed and authenticated
47
+
48
+ ### 1. Install dependencies
49
+ ```bash
50
+ cd slack-claude-code
51
+ poetry install
52
+ ```
53
+
54
+ ### 2. Create Slack App
55
+ Go to https://api.slack.com/apps → "Create New App" → "From scratch"
56
+
57
+ **Socket Mode**: Enable and create an app-level token with `connections:write` scope (save the `xapp-` token)
58
+
59
+ **Bot Token Scopes** (OAuth & Permissions):
60
+ - `chat:write`, `commands`, `channels:history`, `app_mentions:read`, `files:write`
61
+
62
+ **Event Subscriptions**: Enable and add `message.channels`, `app_mention`
63
+
64
+ **App Icon**: In "Basic Information" → "Display Information", upload `assets/claude_logo.png` from this repo as the app icon
65
+
66
+ **Slash Commands** (optional): Create commands like `/clear`, `/model`, `/ls`, `/cd`, `/status`, `/diff`, etc.
67
+
68
+ ### 3. Configure and run
69
+ ```bash
70
+ cp .env.example .env
71
+ # Add your tokens: SLACK_BOT_TOKEN, SLACK_APP_TOKEN, SLACK_SIGNING_SECRET
72
+ poetry run python run.py
73
+ ```
74
+
75
+ ## Usage
76
+
77
+ Type messages in any channel where the bot is present. Each Slack thread maintains an independent Claude session with its own working directory and context.
78
+
79
+ ### Key Features
80
+
81
+ - **Threads = Sessions**: Each thread has isolated context; `/clear` only affects that thread
82
+ - **File Uploads**: Drag & drop files—Claude sees them instantly (code, images, PDFs)
83
+ - **Smart Context**: Frequently-used files are automatically included in prompts
84
+ - **Streaming**: Watch Claude's responses as they're generated
85
+
86
+ ### Plan Mode
87
+
88
+ ```
89
+ /mode plan
90
+ ```
91
+
92
+ Claude creates a detailed plan before execution, shown with Approve/Reject buttons. Ideal for complex implementations where you want to review the approach first.
93
+
94
+
95
+ ## Configuration
96
+
97
+ Key environment variables (see `.env.example` for full list):
98
+
99
+ ```bash
100
+ # Required
101
+ SLACK_BOT_TOKEN=xoxb-...
102
+ SLACK_APP_TOKEN=xapp-...
103
+ SLACK_SIGNING_SECRET=...
104
+
105
+ # Optional
106
+ DEFAULT_WORKING_DIR=/path/to/projects
107
+ COMMAND_TIMEOUT=300 # 5 min default
108
+ CLAUDE_PERMISSION_MODE=approve-all # or: prompt, deny
109
+ AUTO_APPROVE_TOOLS=Read,Glob,Grep,LSP
110
+ ```
111
+
112
+ ## Architecture
113
+
114
+ ```
115
+ src/
116
+ ├── app.py # Main entry point
117
+ ├── config.py # Configuration
118
+ ├── database/ # SQLite persistence (models, migrations, repository)
119
+ ├── claude/ # Claude CLI integration (executor, streaming)
120
+ ├── pty/ # PTY session management (session, pool, parser)
121
+ ├── handlers/ # Slack command handlers
122
+ ├── agents/ # Multi-agent orchestration (planner→worker→evaluator)
123
+ ├── approval/ # Permission & plan approval handling
124
+ ├── git/ # Git operations (status, diff, commit, branch)
125
+ ├── hooks/ # Event hook system
126
+ ├── question/ # AskUserQuestion tool support
127
+ ├── tasks/ # Background task management
128
+ └── utils/ # Formatters, helpers, validators
129
+ ```
130
+
131
+ ## Troubleshooting
132
+
133
+ | Problem | Solution |
134
+ |---------|----------|
135
+ | Configuration errors on startup | Check `.env` has all required tokens |
136
+ | Commands not appearing | Verify slash commands in Slack app settings |
137
+ | Timeouts | Increase `COMMAND_TIMEOUT` |
138
+ | PTY session errors | Use `/pty` → "Restart Session" |
139
+
140
+ ## License
141
+
142
+ MIT
@@ -0,0 +1,29 @@
1
+ [tool.poetry]
2
+ name = "slack-claude-code"
3
+ version = "0.1.1"
4
+ description = "Slack app for running Claude Code CLI commands"
5
+ authors = ["Dan"]
6
+ readme = "README.md"
7
+ packages = [{include = "src"}]
8
+
9
+ [tool.poetry.scripts]
10
+ ccslack = "run:main"
11
+
12
+ [tool.poetry.dependencies]
13
+ python = "^3.10"
14
+ slack-bolt = "^1.27.0"
15
+ aiosqlite = "^0.21.0"
16
+ python-dotenv = "^1.2.1"
17
+ pexpect = "^4.9.0"
18
+ aiohttp = "^3.13.2"
19
+ aiofiles = "^24.1.0"
20
+
21
+
22
+ [tool.poetry.group.dev.dependencies]
23
+ pytest = "^9.0.2"
24
+ pytest-asyncio = "^1.3.0"
25
+ pytest-cov = "^7.0.0"
26
+
27
+ [build-system]
28
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
29
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1 @@
1
+ # Slack Claude Code Bot
@@ -0,0 +1,10 @@
1
+ """Multi-agent workflow orchestration."""
2
+
3
+ from .orchestrator import (
4
+ AgentTask,
5
+ EvalResult,
6
+ MultiAgentOrchestrator,
7
+ TaskStatus,
8
+ WorkflowResult,
9
+ )
10
+ from .roles import AgentConfig, AgentRole
@@ -0,0 +1,316 @@
1
+ """Multi-agent workflow orchestrator.
2
+
3
+ Coordinates Planner -> Worker -> Evaluator pipeline for complex tasks.
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ from dataclasses import dataclass, field
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Awaitable, Callable, Optional
12
+
13
+ from ..claude.subprocess_executor import ExecutionResult, SubprocessExecutor
14
+ from .roles import AgentRole, format_task_prompt
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class TaskStatus(Enum):
20
+ """Status of an agent task."""
21
+
22
+ PENDING = "pending"
23
+ PLANNING = "planning"
24
+ WORKING = "working"
25
+ EVALUATING = "evaluating"
26
+ COMPLETED = "completed"
27
+ FAILED = "failed"
28
+ CANCELLED = "cancelled"
29
+
30
+
31
+ class EvalResult(Enum):
32
+ """Evaluation result from evaluator agent."""
33
+
34
+ COMPLETE = "complete"
35
+ PARTIAL = "partial"
36
+ INCOMPLETE = "incomplete"
37
+ FAILED = "failed"
38
+
39
+
40
+ @dataclass
41
+ class AgentTask:
42
+ """A task being processed by the multi-agent workflow."""
43
+
44
+ task_id: str
45
+ description: str
46
+ channel_id: str
47
+ working_directory: str = "~"
48
+ status: TaskStatus = TaskStatus.PENDING
49
+ plan_output: Optional[str] = None
50
+ work_output: Optional[str] = None
51
+ eval_output: Optional[str] = None
52
+ eval_result: Optional[EvalResult] = None
53
+ error_message: Optional[str] = None
54
+ turn_count: int = 0
55
+ max_turns: int = 50
56
+ slack_thread_ts: Optional[str] = None
57
+ message_ts: Optional[str] = None
58
+ created_at: datetime = field(default_factory=datetime.now)
59
+ started_at: Optional[datetime] = None
60
+ completed_at: Optional[datetime] = None
61
+
62
+
63
+ @dataclass
64
+ class WorkflowResult:
65
+ """Result of a complete workflow execution."""
66
+
67
+ task: AgentTask
68
+ success: bool
69
+ plan: Optional[str] = None
70
+ work_output: Optional[str] = None
71
+ evaluation: Optional[str] = None
72
+ eval_result: Optional[EvalResult] = None
73
+ total_turns: int = 0
74
+ duration_ms: Optional[int] = None
75
+
76
+
77
+ class MultiAgentOrchestrator:
78
+ """Orchestrates multi-agent workflows.
79
+
80
+ Runs tasks through Planner -> Worker -> Evaluator pipeline.
81
+ Each agent uses its own isolated PTY session.
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ executor: SubprocessExecutor,
87
+ max_iterations: int = 3,
88
+ ) -> None:
89
+ """Initialize orchestrator.
90
+
91
+ Args:
92
+ executor: Claude executor for running agents
93
+ max_iterations: Max worker-evaluator iterations
94
+ """
95
+ self.executor = executor
96
+ self.max_iterations = max_iterations
97
+ self._active_tasks: dict[str, AgentTask] = {}
98
+
99
+ async def execute_workflow(
100
+ self,
101
+ task: AgentTask,
102
+ on_status_update: Optional[Callable[[AgentTask], Awaitable[None]]] = None,
103
+ ) -> WorkflowResult:
104
+ """Execute the full multi-agent workflow.
105
+
106
+ Args:
107
+ task: The task to execute
108
+ on_status_update: Optional callback for status updates
109
+
110
+ Returns:
111
+ WorkflowResult with outcomes
112
+ """
113
+ self._active_tasks[task.task_id] = task
114
+ task.started_at = datetime.now()
115
+ start_time = asyncio.get_running_loop().time()
116
+
117
+ try:
118
+ # Phase 1: Planning
119
+ task.status = TaskStatus.PLANNING
120
+ if on_status_update:
121
+ await on_status_update(task)
122
+
123
+ plan_result = await self._run_planner(task)
124
+ if not plan_result.success:
125
+ task.status = TaskStatus.FAILED
126
+ task.error_message = plan_result.error
127
+ return self._create_result(task, False, start_time)
128
+
129
+ task.plan_output = plan_result.output
130
+ task.turn_count += 1
131
+
132
+ # Phase 2: Working (with potential re-iterations)
133
+ for iteration in range(self.max_iterations):
134
+ task.status = TaskStatus.WORKING
135
+ if on_status_update:
136
+ await on_status_update(task)
137
+
138
+ work_result = await self._run_worker(task)
139
+ if not work_result.success:
140
+ task.status = TaskStatus.FAILED
141
+ task.error_message = work_result.error
142
+ return self._create_result(task, False, start_time)
143
+
144
+ task.work_output = work_result.output
145
+ task.turn_count += 1
146
+
147
+ # Phase 3: Evaluation
148
+ task.status = TaskStatus.EVALUATING
149
+ if on_status_update:
150
+ await on_status_update(task)
151
+
152
+ eval_result = await self._run_evaluator(task)
153
+ task.eval_output = eval_result.output
154
+ task.turn_count += 1
155
+
156
+ # Parse evaluation result
157
+ eval_verdict = self._parse_eval_result(eval_result.output)
158
+ task.eval_result = eval_verdict
159
+
160
+ if eval_verdict == EvalResult.COMPLETE:
161
+ task.status = TaskStatus.COMPLETED
162
+ task.completed_at = datetime.now()
163
+ return self._create_result(task, True, start_time)
164
+
165
+ elif eval_verdict == EvalResult.FAILED:
166
+ task.status = TaskStatus.FAILED
167
+ task.error_message = "Evaluation returned FAILED"
168
+ return self._create_result(task, False, start_time)
169
+
170
+ # For PARTIAL or INCOMPLETE, continue to next iteration
171
+ logger.info(
172
+ f"Task {task.task_id} eval: {eval_verdict.value}, "
173
+ f"iteration {iteration + 1}/{self.max_iterations}"
174
+ )
175
+
176
+ # Max iterations reached
177
+ task.status = TaskStatus.COMPLETED
178
+ task.completed_at = datetime.now()
179
+ return self._create_result(task, True, start_time)
180
+
181
+ except asyncio.CancelledError:
182
+ task.status = TaskStatus.CANCELLED
183
+ raise
184
+
185
+ except Exception as e:
186
+ task.status = TaskStatus.FAILED
187
+ task.error_message = str(e)
188
+ logger.error(f"Workflow failed for task {task.task_id}: {e}")
189
+ return self._create_result(task, False, start_time)
190
+
191
+ finally:
192
+ self._active_tasks.pop(task.task_id, None)
193
+ # Cleanup agent sessions
194
+ await self._cleanup_sessions(task.task_id)
195
+
196
+ async def _run_planner(self, task: AgentTask) -> ExecutionResult:
197
+ """Run the planner agent."""
198
+ prompt = format_task_prompt(
199
+ role=AgentRole.PLANNER,
200
+ task=task.description,
201
+ working_directory=task.working_directory,
202
+ )
203
+
204
+ # Use task-specific session for planner
205
+ session_id = f"{task.task_id}-planner"
206
+
207
+ return await self.executor.execute(
208
+ prompt=prompt,
209
+ working_directory=task.working_directory,
210
+ session_id=session_id,
211
+ execution_id=session_id,
212
+ )
213
+
214
+ async def _run_worker(self, task: AgentTask) -> ExecutionResult:
215
+ """Run the worker agent."""
216
+ prompt = format_task_prompt(
217
+ role=AgentRole.WORKER,
218
+ task=task.description,
219
+ plan=task.plan_output,
220
+ )
221
+
222
+ # Use task-specific session for worker
223
+ session_id = f"{task.task_id}-worker"
224
+
225
+ return await self.executor.execute(
226
+ prompt=prompt,
227
+ working_directory=task.working_directory,
228
+ session_id=session_id,
229
+ execution_id=session_id,
230
+ )
231
+
232
+ async def _run_evaluator(self, task: AgentTask) -> ExecutionResult:
233
+ """Run the evaluator agent."""
234
+ prompt = format_task_prompt(
235
+ role=AgentRole.EVALUATOR,
236
+ task=task.description,
237
+ plan=task.plan_output,
238
+ work_output=task.work_output,
239
+ )
240
+
241
+ # Use task-specific session for evaluator
242
+ session_id = f"{task.task_id}-evaluator"
243
+
244
+ return await self.executor.execute(
245
+ prompt=prompt,
246
+ working_directory=task.working_directory,
247
+ session_id=session_id,
248
+ execution_id=session_id,
249
+ )
250
+
251
+ def _parse_eval_result(self, output: str) -> EvalResult:
252
+ """Parse evaluation verdict from output."""
253
+ output_upper = output.upper()
254
+
255
+ if "COMPLETE" in output_upper and "INCOMPLETE" not in output_upper:
256
+ return EvalResult.COMPLETE
257
+ elif "INCOMPLETE" in output_upper:
258
+ return EvalResult.INCOMPLETE
259
+ elif "PARTIAL" in output_upper:
260
+ return EvalResult.PARTIAL
261
+ elif "FAILED" in output_upper or "FAIL" in output_upper:
262
+ return EvalResult.FAILED
263
+
264
+ # Default to partial if can't determine
265
+ return EvalResult.PARTIAL
266
+
267
+ def _create_result(
268
+ self,
269
+ task: AgentTask,
270
+ success: bool,
271
+ start_time: float,
272
+ ) -> WorkflowResult:
273
+ """Create a workflow result."""
274
+ duration_ms = int((asyncio.get_running_loop().time() - start_time) * 1000)
275
+
276
+ return WorkflowResult(
277
+ task=task,
278
+ success=success,
279
+ plan=task.plan_output,
280
+ work_output=task.work_output,
281
+ evaluation=task.eval_output,
282
+ eval_result=task.eval_result,
283
+ total_turns=task.turn_count,
284
+ duration_ms=duration_ms,
285
+ )
286
+
287
+ async def _cleanup_sessions(self, task_id: str) -> None:
288
+ """Cancel any active subprocess executions for a task."""
289
+ for role in ["planner", "worker", "evaluator"]:
290
+ session_id = f"{task_id}-{role}"
291
+ await self.executor.cancel(session_id)
292
+
293
+ async def cancel_task(self, task_id: str) -> bool:
294
+ """Cancel an active task.
295
+
296
+ Args:
297
+ task_id: The task ID to cancel
298
+
299
+ Returns:
300
+ True if task was found and cancelled
301
+ """
302
+ task = self._active_tasks.get(task_id)
303
+ if not task:
304
+ return False
305
+
306
+ task.status = TaskStatus.CANCELLED
307
+ await self._cleanup_sessions(task_id)
308
+ return True
309
+
310
+ def get_active_tasks(self) -> list[AgentTask]:
311
+ """Get list of active tasks."""
312
+ return list(self._active_tasks.values())
313
+
314
+ def get_task(self, task_id: str) -> Optional[AgentTask]:
315
+ """Get a specific task by ID."""
316
+ return self._active_tasks.get(task_id)