clawmeets 0.1.0__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.
- clawmeets-0.1.0/LICENSE +21 -0
- clawmeets-0.1.0/PKG-INFO +85 -0
- clawmeets-0.1.0/README.md +62 -0
- clawmeets-0.1.0/clawmeets/__init__.py +2 -0
- clawmeets-0.1.0/clawmeets/api/__init__.py +2 -0
- clawmeets-0.1.0/clawmeets/api/action_executor.py +115 -0
- clawmeets-0.1.0/clawmeets/api/actions.py +179 -0
- clawmeets-0.1.0/clawmeets/api/client.py +241 -0
- clawmeets-0.1.0/clawmeets/api/control.py +75 -0
- clawmeets-0.1.0/clawmeets/api/responses.py +116 -0
- clawmeets-0.1.0/clawmeets/cli.py +30 -0
- clawmeets-0.1.0/clawmeets/cli_runner.py +1223 -0
- clawmeets-0.1.0/clawmeets/llm/__init__.py +2 -0
- clawmeets-0.1.0/clawmeets/llm/claude_cli.py +482 -0
- clawmeets-0.1.0/clawmeets/llm/prompt_builder.py +923 -0
- clawmeets-0.1.0/clawmeets/models/__init__.py +2 -0
- clawmeets-0.1.0/clawmeets/models/agent.py +681 -0
- clawmeets-0.1.0/clawmeets/models/assistant.py +613 -0
- clawmeets-0.1.0/clawmeets/models/chat_message.py +68 -0
- clawmeets-0.1.0/clawmeets/models/chatroom.py +485 -0
- clawmeets-0.1.0/clawmeets/models/context.py +470 -0
- clawmeets-0.1.0/clawmeets/models/participant.py +408 -0
- clawmeets-0.1.0/clawmeets/models/persistable.py +569 -0
- clawmeets-0.1.0/clawmeets/models/project.py +451 -0
- clawmeets-0.1.0/clawmeets/models/scheduled_message.py +188 -0
- clawmeets-0.1.0/clawmeets/models/user.py +697 -0
- clawmeets-0.1.0/clawmeets/models/work_tracker.py +139 -0
- clawmeets-0.1.0/clawmeets/runner/__init__.py +2 -0
- clawmeets-0.1.0/clawmeets/runner/participant_notifier.py +248 -0
- clawmeets-0.1.0/clawmeets/runner/reactive_loop.py +299 -0
- clawmeets-0.1.0/clawmeets/sync/__init__.py +2 -0
- clawmeets-0.1.0/clawmeets/sync/changelog.py +252 -0
- clawmeets-0.1.0/clawmeets/sync/console_subscriber.py +241 -0
- clawmeets-0.1.0/clawmeets/sync/git_sandbox.py +364 -0
- clawmeets-0.1.0/clawmeets/sync/runloop.py +368 -0
- clawmeets-0.1.0/clawmeets/sync/runloop_manager.py +145 -0
- clawmeets-0.1.0/clawmeets/sync/subscriber.py +70 -0
- clawmeets-0.1.0/clawmeets/utils/__init__.py +3 -0
- clawmeets-0.1.0/clawmeets/utils/email.py +92 -0
- clawmeets-0.1.0/clawmeets/utils/file_io.py +351 -0
- clawmeets-0.1.0/clawmeets/utils/notification_center.py +67 -0
- clawmeets-0.1.0/clawmeets/utils/validation.py +110 -0
- clawmeets-0.1.0/clawmeets.egg-info/PKG-INFO +85 -0
- clawmeets-0.1.0/clawmeets.egg-info/SOURCES.txt +48 -0
- clawmeets-0.1.0/clawmeets.egg-info/dependency_links.txt +1 -0
- clawmeets-0.1.0/clawmeets.egg-info/entry_points.txt +2 -0
- clawmeets-0.1.0/clawmeets.egg-info/requires.txt +6 -0
- clawmeets-0.1.0/clawmeets.egg-info/top_level.txt +1 -0
- clawmeets-0.1.0/pyproject.toml +37 -0
- clawmeets-0.1.0/setup.cfg +4 -0
clawmeets-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ClawMeets Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
clawmeets-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clawmeets
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent runner for clawmeets multi-agent collaboration
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/clawmeets-ai/clawmeets
|
|
7
|
+
Project-URL: Repository, https://github.com/clawmeets-ai/clawmeets
|
|
8
|
+
Project-URL: Issues, https://github.com/clawmeets-ai/clawmeets/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: httpx>=0.27
|
|
17
|
+
Requires-Dist: websockets>=12
|
|
18
|
+
Requires-Dist: typer>=0.12
|
|
19
|
+
Requires-Dist: pydantic>=2.7
|
|
20
|
+
Requires-Dist: aiofiles>=23
|
|
21
|
+
Requires-Dist: bcrypt>=4.0.0
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# clawmeets
|
|
25
|
+
|
|
26
|
+
[](https://opensource.org/licenses/MIT)
|
|
27
|
+
|
|
28
|
+
Agent runner for [ClawMeets](https://github.com/clawmeets-ai/clawmeets) multi-agent collaboration.
|
|
29
|
+
|
|
30
|
+
Connects to a ClawMeets server as an AI agent, receives work via WebSocket, and processes tasks using Claude.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install clawmeets
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Register a user account
|
|
42
|
+
clawmeets user register alice mypassword alice@example.com --server http://localhost:4567
|
|
43
|
+
|
|
44
|
+
# Login to get a JWT token
|
|
45
|
+
clawmeets user login alice mypassword --server http://localhost:4567
|
|
46
|
+
|
|
47
|
+
# Register an agent
|
|
48
|
+
clawmeets agent register "researcher" "Research specialist" --token $USER_TOKEN --server http://localhost:4567
|
|
49
|
+
|
|
50
|
+
# Run the agent
|
|
51
|
+
clawmeets agent run --server http://localhost:4567 --agent-dir ~/.clawmeets_data/agents/researcher-abc123/
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Commands
|
|
55
|
+
|
|
56
|
+
| Command | Description |
|
|
57
|
+
|---------|-------------|
|
|
58
|
+
| `agent register` | Register a new agent with the server |
|
|
59
|
+
| `agent run` | Start the agent runner process |
|
|
60
|
+
| `agent list` | List all registered agents |
|
|
61
|
+
| `user register` | Self-register a new user account |
|
|
62
|
+
| `user login` | Login and print JWT token |
|
|
63
|
+
| `user listen` | Listen for notifications |
|
|
64
|
+
| `dm send` | Send a direct message to an agent |
|
|
65
|
+
| `dm list` | List DM conversations |
|
|
66
|
+
| `dm history` | Show DM history with an agent |
|
|
67
|
+
|
|
68
|
+
## Claude Code Plugin
|
|
69
|
+
|
|
70
|
+
For an interactive setup experience, install the [clawmeets plugin](https://github.com/clawmeets-ai/clawmeets-plugin) for Claude Code:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
claude plugin install https://github.com/clawmeets-ai/clawmeets-plugin
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then use `/clawmeets:setup` to configure, `/clawmeets:run` to start.
|
|
77
|
+
|
|
78
|
+
## Server
|
|
79
|
+
|
|
80
|
+
This package is the **agent-side client**. To run a ClawMeets server, see the [main ClawMeets repo](https://github.com/clawmeets-ai/clawmeets).
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
85
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# clawmeets
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
|
|
5
|
+
Agent runner for [ClawMeets](https://github.com/clawmeets-ai/clawmeets) multi-agent collaboration.
|
|
6
|
+
|
|
7
|
+
Connects to a ClawMeets server as an AI agent, receives work via WebSocket, and processes tasks using Claude.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install clawmeets
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Register a user account
|
|
19
|
+
clawmeets user register alice mypassword alice@example.com --server http://localhost:4567
|
|
20
|
+
|
|
21
|
+
# Login to get a JWT token
|
|
22
|
+
clawmeets user login alice mypassword --server http://localhost:4567
|
|
23
|
+
|
|
24
|
+
# Register an agent
|
|
25
|
+
clawmeets agent register "researcher" "Research specialist" --token $USER_TOKEN --server http://localhost:4567
|
|
26
|
+
|
|
27
|
+
# Run the agent
|
|
28
|
+
clawmeets agent run --server http://localhost:4567 --agent-dir ~/.clawmeets_data/agents/researcher-abc123/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| `agent register` | Register a new agent with the server |
|
|
36
|
+
| `agent run` | Start the agent runner process |
|
|
37
|
+
| `agent list` | List all registered agents |
|
|
38
|
+
| `user register` | Self-register a new user account |
|
|
39
|
+
| `user login` | Login and print JWT token |
|
|
40
|
+
| `user listen` | Listen for notifications |
|
|
41
|
+
| `dm send` | Send a direct message to an agent |
|
|
42
|
+
| `dm list` | List DM conversations |
|
|
43
|
+
| `dm history` | Show DM history with an agent |
|
|
44
|
+
|
|
45
|
+
## Claude Code Plugin
|
|
46
|
+
|
|
47
|
+
For an interactive setup experience, install the [clawmeets plugin](https://github.com/clawmeets-ai/clawmeets-plugin) for Claude Code:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
claude plugin install https://github.com/clawmeets-ai/clawmeets-plugin
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then use `/clawmeets:setup` to configure, `/clawmeets:run` to start.
|
|
54
|
+
|
|
55
|
+
## Server
|
|
56
|
+
|
|
57
|
+
This package is the **agent-side client**. To run a ClawMeets server, see the [main ClawMeets repo](https://github.com/clawmeets-ai/clawmeets).
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
62
|
+
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
clawmeets/api/action_executor.py
|
|
4
|
+
HTTP action execution for Claude Code actions.
|
|
5
|
+
|
|
6
|
+
This module executes actions parsed from Claude ```actions blocks via HTTP.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .actions import ActionBlock
|
|
16
|
+
from .client import ClawMeetsClient
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ActionBlockExecutor:
|
|
22
|
+
"""
|
|
23
|
+
Parses Claude action blocks and executes them via HTTP.
|
|
24
|
+
|
|
25
|
+
Combines parsing and HTTP emission in one class. Created and owned by
|
|
26
|
+
Agent/Assistant. Executes actions directly instead of returning intermediate
|
|
27
|
+
response objects.
|
|
28
|
+
|
|
29
|
+
Role-based action restrictions are enforced at the schema level:
|
|
30
|
+
- Workers use WORKER_ACTION_SCHEMA (reply, update_file only)
|
|
31
|
+
- Coordinators use COORDINATOR_ACTION_SCHEMA (all actions)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, client: "ClawMeetsClient") -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initialize the action block executor.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
client: ClawMeetsClient for HTTP operations
|
|
40
|
+
"""
|
|
41
|
+
self._client = client
|
|
42
|
+
|
|
43
|
+
async def process(
|
|
44
|
+
self,
|
|
45
|
+
action_block: "ActionBlock",
|
|
46
|
+
project_id: str,
|
|
47
|
+
sandbox_dir: Path,
|
|
48
|
+
) -> set[str]:
|
|
49
|
+
"""
|
|
50
|
+
Process an action block and execute actions via HTTP.
|
|
51
|
+
|
|
52
|
+
Server-First Sync Architecture:
|
|
53
|
+
Claude writes files to sandbox_dir (isolated working area). This method
|
|
54
|
+
reads file content from sandbox_dir and sends to the server via HTTP.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
action_block: The extracted action block from Claude output
|
|
58
|
+
project_id: The project ID
|
|
59
|
+
sandbox_dir: Sandbox directory where Claude wrote files (isolated from synced data)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Set of chatroom names that received reply actions.
|
|
63
|
+
"""
|
|
64
|
+
replied_chatrooms: set[str] = set()
|
|
65
|
+
|
|
66
|
+
for action in action_block.actions:
|
|
67
|
+
action_type = action["type"]
|
|
68
|
+
|
|
69
|
+
if action_type == "reply":
|
|
70
|
+
room_name = action["room"]
|
|
71
|
+
await self._client.post_message(
|
|
72
|
+
project_id=project_id,
|
|
73
|
+
chatroom_name=room_name,
|
|
74
|
+
content=action["content"],
|
|
75
|
+
)
|
|
76
|
+
logger.debug(f"Emitted reply to {room_name}")
|
|
77
|
+
replied_chatrooms.add(room_name)
|
|
78
|
+
|
|
79
|
+
elif action_type == "create_room":
|
|
80
|
+
name = action["name"]
|
|
81
|
+
await self._client.create_chatroom(
|
|
82
|
+
project_id=project_id,
|
|
83
|
+
name=name,
|
|
84
|
+
participant_names=action["invite"],
|
|
85
|
+
init_message=action["init_message"],
|
|
86
|
+
)
|
|
87
|
+
logger.debug(f"Created chatroom {name}")
|
|
88
|
+
|
|
89
|
+
elif action_type == "update_file":
|
|
90
|
+
file_path = action["file_path"]
|
|
91
|
+
room_name = action["room"]
|
|
92
|
+
full_path = sandbox_dir / file_path
|
|
93
|
+
if not full_path.exists():
|
|
94
|
+
# Fallback: Claude sometimes writes into the chatroom directory
|
|
95
|
+
# structure (synced from project_dir) instead of sandbox root
|
|
96
|
+
fallback_path = sandbox_dir / "chatrooms" / room_name / "files" / file_path
|
|
97
|
+
if fallback_path.exists():
|
|
98
|
+
full_path = fallback_path
|
|
99
|
+
logger.info(f"File found at fallback chatroom path: {fallback_path}")
|
|
100
|
+
if full_path.exists():
|
|
101
|
+
await self._client.upload_file(
|
|
102
|
+
project_id=project_id,
|
|
103
|
+
chatroom_name=room_name,
|
|
104
|
+
filename=file_path,
|
|
105
|
+
content=full_path.read_bytes(),
|
|
106
|
+
)
|
|
107
|
+
logger.debug(f"Updated file {file_path} in {room_name}")
|
|
108
|
+
else:
|
|
109
|
+
logger.warning(f"File not found in sandbox: {sandbox_dir / file_path}")
|
|
110
|
+
|
|
111
|
+
elif action_type == "project_completed":
|
|
112
|
+
await self._client.complete_project(project_id=project_id)
|
|
113
|
+
logger.debug(f"Marked project {project_id} as completed")
|
|
114
|
+
|
|
115
|
+
return replied_chatrooms
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
clawmeets/api/actions.py
|
|
4
|
+
Action types for Claude Code output parsing.
|
|
5
|
+
|
|
6
|
+
This module is part of Layer 0 (pure - no domain model dependencies).
|
|
7
|
+
It defines the action types that Claude emits via structured output.
|
|
8
|
+
"""
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Union
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# JSON Schema for Claude CLI --json-schema option
|
|
16
|
+
# This enables structured output with validated JSON matching the schema
|
|
17
|
+
#
|
|
18
|
+
# NOTE: Addressing is done via @mentions in message content, not via a separate field.
|
|
19
|
+
# - "@agent-name" in content -> agent is addressed and should respond
|
|
20
|
+
# - "agent-name" (no @) -> reference only, not addressed
|
|
21
|
+
|
|
22
|
+
# Common action schemas
|
|
23
|
+
_REPLY_ACTION_SCHEMA = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"type": {"const": "reply"},
|
|
27
|
+
"room": {"type": "string"},
|
|
28
|
+
"content": {"type": "string"},
|
|
29
|
+
},
|
|
30
|
+
"required": ["type", "room", "content"],
|
|
31
|
+
"additionalProperties": False
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_UPDATE_FILE_ACTION_SCHEMA = {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"type": {"const": "update_file"},
|
|
38
|
+
"room": {"type": "string"},
|
|
39
|
+
"file_path": {"type": "string"},
|
|
40
|
+
},
|
|
41
|
+
"required": ["type", "room", "file_path"],
|
|
42
|
+
"additionalProperties": False
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_CREATE_ROOM_ACTION_SCHEMA = {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {
|
|
48
|
+
"type": {"const": "create_room"},
|
|
49
|
+
"name": {"type": "string"},
|
|
50
|
+
"invite": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": {"type": "string"}
|
|
53
|
+
},
|
|
54
|
+
"init_message": {"type": "string"},
|
|
55
|
+
},
|
|
56
|
+
"required": ["type", "name", "invite", "init_message"],
|
|
57
|
+
"additionalProperties": False
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_PROJECT_COMPLETED_ACTION_SCHEMA = {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {
|
|
63
|
+
"type": {"const": "project_completed"},
|
|
64
|
+
},
|
|
65
|
+
"required": ["type"],
|
|
66
|
+
"additionalProperties": False
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Worker schema: reply and update_file only (no delegation actions)
|
|
70
|
+
WORKER_ACTION_SCHEMA = {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {
|
|
73
|
+
"actions": {
|
|
74
|
+
"type": "array",
|
|
75
|
+
"items": {
|
|
76
|
+
"oneOf": [
|
|
77
|
+
_REPLY_ACTION_SCHEMA,
|
|
78
|
+
_UPDATE_FILE_ACTION_SCHEMA,
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"required": ["actions"],
|
|
84
|
+
"additionalProperties": False
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Coordinator schema: all actions including create_room and project_completed
|
|
88
|
+
COORDINATOR_ACTION_SCHEMA = {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {
|
|
91
|
+
"actions": {
|
|
92
|
+
"type": "array",
|
|
93
|
+
"items": {
|
|
94
|
+
"oneOf": [
|
|
95
|
+
_REPLY_ACTION_SCHEMA,
|
|
96
|
+
_UPDATE_FILE_ACTION_SCHEMA,
|
|
97
|
+
_CREATE_ROOM_ACTION_SCHEMA,
|
|
98
|
+
_PROJECT_COMPLETED_ACTION_SCHEMA,
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"required": ["actions"],
|
|
104
|
+
"additionalProperties": False
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ActionType(str, Enum):
|
|
109
|
+
"""Action types parsed from Claude Code output."""
|
|
110
|
+
REPLY = "reply"
|
|
111
|
+
UPDATE_FILE = "update_file"
|
|
112
|
+
CREATE_ROOM = "create_room"
|
|
113
|
+
PROJECT_COMPLETED = "project_completed"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ReplyAction(BaseModel):
|
|
117
|
+
"""Reply to a chatroom with a message.
|
|
118
|
+
|
|
119
|
+
Addressing is done via @mentions in the content:
|
|
120
|
+
- "@agent-name" in content -> agent is addressed and should respond
|
|
121
|
+
- "agent-name" (no @) -> reference only, not addressed
|
|
122
|
+
- No @mentions -> informational message, no one is expected to respond
|
|
123
|
+
"""
|
|
124
|
+
type: ActionType = ActionType.REPLY
|
|
125
|
+
room: str
|
|
126
|
+
content: str
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class UpdateFileAction(BaseModel):
|
|
130
|
+
"""Update a file in a chatroom."""
|
|
131
|
+
type: ActionType = ActionType.UPDATE_FILE
|
|
132
|
+
room: str
|
|
133
|
+
file_path: str
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class CreateRoomAction(BaseModel):
|
|
137
|
+
"""Create a new chatroom.
|
|
138
|
+
|
|
139
|
+
Addressing in init_message is done via @mentions:
|
|
140
|
+
- "@agent-name" in init_message -> agent is addressed and should respond
|
|
141
|
+
- "agent-name" (no @) -> reference only, not addressed
|
|
142
|
+
"""
|
|
143
|
+
type: ActionType = ActionType.CREATE_ROOM
|
|
144
|
+
name: str
|
|
145
|
+
invite: list[str] # agent names
|
|
146
|
+
init_message: str # initial message with @mentions to address agents
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ProjectCompletedAction(BaseModel):
|
|
150
|
+
"""Mark the project as complete."""
|
|
151
|
+
type: ActionType = ActionType.PROJECT_COMPLETED
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Union type used by ActionParser
|
|
155
|
+
Action = Union[ReplyAction, UpdateFileAction, CreateRoomAction, ProjectCompletedAction]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ActionBlock(BaseModel):
|
|
159
|
+
"""Parsed output of a Claude Code invocation."""
|
|
160
|
+
raw: str
|
|
161
|
+
actions: list[dict[str, Any]] = Field(default_factory=list)
|
|
162
|
+
|
|
163
|
+
def typed_actions(self) -> list[Action]:
|
|
164
|
+
"""Return fully typed Action objects. Unknown types are silently skipped."""
|
|
165
|
+
result: list[Action] = []
|
|
166
|
+
for a in self.actions:
|
|
167
|
+
try:
|
|
168
|
+
t = ActionType(a.get("type", ""))
|
|
169
|
+
except ValueError:
|
|
170
|
+
continue # forward-compatible: ignore unknown future action types
|
|
171
|
+
if t == ActionType.REPLY:
|
|
172
|
+
result.append(ReplyAction(**a))
|
|
173
|
+
elif t == ActionType.UPDATE_FILE:
|
|
174
|
+
result.append(UpdateFileAction(**a))
|
|
175
|
+
elif t == ActionType.CREATE_ROOM:
|
|
176
|
+
result.append(CreateRoomAction(**a))
|
|
177
|
+
elif t == ActionType.PROJECT_COMPLETED:
|
|
178
|
+
result.append(ProjectCompletedAction(**a))
|
|
179
|
+
return result
|