claude-comms 1.0.45 → 2.0.0
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.
- package/CHANGELOG.md +20 -1
- package/README.md +12 -13
- package/bin/claude-setup.js +3 -4
- package/package.json +6 -10
- package/src/orchestrator/installer.js +5 -63
- package/.agents/AGENTS.md +0 -61
- package/.agents/repo.md +0 -18
- package/.agents/tools/README.md +0 -45
- package/.agents/tools/agent-job/README.md +0 -208
- package/.agents/tools/agent-job/agent_job.py +0 -866
- package/.agents/tools/agent-job/agent_monitor.py +0 -575
- package/.agents/tools/agent-job/consultant_template.txt +0 -73
- package/.agents/tools/chrome-devtools/README.md +0 -101
- package/.agents/tools/chrome-devtools/browsertools.py +0 -839
- package/.agents/tools/chrome-devtools/config.json +0 -24
- package/.claude/agents/core/architect.md +0 -316
- package/.claude/agents/core/consultants/codex.md +0 -39
- package/.claude/agents/core/consultants/gemini.md +0 -39
- package/.claude/agents/core/designer.md +0 -181
- package/.claude/agents/core/engineer.md +0 -157
- package/.claude/agents/core/planner.md +0 -251
- package/.claude/agents/core/uat.md +0 -147
- package/.claude/agents/util/feedback-executor.md +0 -29
- package/.claude/agents/util/meta-agent-builder.md +0 -309
- package/.claude/agents/util/session-review.md +0 -228
- package/.claude/commands/action-feedback-item.md +0 -22
- package/.claude/commands/analyse-document.md +0 -10
- package/.claude/commands/await-feedback-orchestrator.md +0 -12
- package/.claude/commands/await-feedback.md +0 -21
- package/.claude/commands/cook.md +0 -119
- package/.claude/commands/feedback.md +0 -140
- package/.claude/commands/hello-world.md +0 -1
- package/.claude/commands/install-feedback-widget.md +0 -599
- package/.claude/commands/learn.md +0 -32
- package/.claude/commands/uat-refine.md +0 -121
- package/.claude/hooks/comms/get_unread_messages.py +0 -71
- package/.claude/hooks/comms/register_subagent.py +0 -74
- package/.claude/hooks/comms/send_message.py +0 -87
- package/.claude/hooks/comms/update_subagent_completion.py +0 -126
- package/.claude/hooks/context/session_id.py +0 -35
- package/.claude/hooks/observability/send_event.py +0 -110
- package/.claude/hooks/session-data/get_session_data.py +0 -261
- package/.claude/hooks/session-data/intercept_session_id.py +0 -69
- package/.claude/hooks/utils/server_config.py +0 -17
- package/.claude/settings.json +0 -108
- package/CLAUDE.md +0 -240
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
Primary ⚙️Orchestrator Assignment Orchestration
|
|
2
|
-
|
|
3
|
-
You are Primary ⚙️Orchestrator.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ULTRATHINK about your CRITICAL ORCHESTRATION PROTOCOLS, and apply ⚡ORCHESTRATION untiil complete solution is delivered for your ASSIGNMENT:
|
|
7
|
-
[ASSIGNMENT]
|
|
8
|
-
#$ARGUMENTS
|
|
9
|
-
|
|
10
|
-
[/ASSIGNMENT]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# ⚡ORCHESTRATION
|
|
15
|
-
## EXECUTION SEQUENCE
|
|
16
|
-
You must manage and maintain Todos dynamically, refine Todos after every decision, and when new information presents itself.
|
|
17
|
-
Populate your initial Todos with your step by step WORKFLOW:
|
|
18
|
-
|
|
19
|
-
[WORKFLOW]
|
|
20
|
-
PLAN: ULTRATHINK about the UI/UX pages, features, user flows involved and the dependencies between. Break down ASSIGNMENT into dependency sequenced test scenarios batches.
|
|
21
|
-
ENABLE: ensure the dev server is running. you may need to kill an old process and start it again `.agents/repo.md`
|
|
22
|
-
|
|
23
|
-
For each test scenario batch sequence:
|
|
24
|
-
1. FEEDBACK: Launch a batch of multiple UAT agents and a support designer. Each UAT agent in the batch should be given a focused test scenario or portion of the app to review. they must visually inspect the UI/UX and provide feedback on: design allignment, expected functionality, issues encountered, error logs. These UAT agents are proxy for the user. Their perspecttive matters!
|
|
25
|
-
2. IMPLEMENT: Launch a blended Implementation batch of engineers + codex consultants + gemini consultants to implement changes based on UAT FEEDBACK.
|
|
26
|
-
3. REQUEST APPROVAL: Task a batch of one or more (depending on the number of impacted areas/changes) UAT agents. Each UAT agent in this batch should manually execute one of the test scenarios/areas impacted, and provide their assessment of changes, design allignment, expected functionality.
|
|
27
|
-
- If UAT gives critical feedback, and DOES NOT APPROVE: itterate until approval awarded.
|
|
28
|
-
- When all UAT agents in the batch give signoff approval for deployment, continue to the next to the next batch of test scenarios
|
|
29
|
-
[/WORKFLOW]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## 🎯 CRITICAL EXECUTION RULES
|
|
33
|
-
|
|
34
|
-
### Dependency Laws
|
|
35
|
-
⚡ **ALWAYS** Take UAT feedback seriously. They are the user's representitive.
|
|
36
|
-
⚡ **ALWAYS** Add new todos if Approval is not awarded.
|
|
37
|
-
⚡ **ALWAYS** blend consultants + engineers for implementation/refinement tasks
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
### Agent Instructions Template
|
|
42
|
-
```
|
|
43
|
-
"Your name is [FirstNameLastName].
|
|
44
|
-
Your Team Role is [Support/Implementation/Review+Refine/AssessingOnly]
|
|
45
|
-
|
|
46
|
-
SCOPE: [Phase-level (phase-id: XX-Name)]
|
|
47
|
-
|
|
48
|
-
YOUR TASK:
|
|
49
|
-
[Specific task description]
|
|
50
|
-
|
|
51
|
-
CONSTRAINTS:
|
|
52
|
-
[Any dependencies, interfaces, or requirements]
|
|
53
|
-
|
|
54
|
-
SUCCESS CRITERIA:
|
|
55
|
-
[What constitutes completion]
|
|
56
|
-
|
|
57
|
-
FILES TO READ FIRST:
|
|
58
|
-
- [filepath1] - [one sentence description]
|
|
59
|
-
- [filepath2] - [one sentence description]
|
|
60
|
-
|
|
61
|
-
TEAM COLLABORATION:
|
|
62
|
-
- Leverage your Team for [what]
|
|
63
|
-
- Support your Team with [what]
|
|
64
|
-
- Coordinate with your Team on [shared concern]
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
⭐*The successful delivery of your assigned task, contributes to the high level Assignment:*⭐
|
|
68
|
-
[Your full ASSIGNMENT]
|
|
69
|
-
|
|
70
|
-
⭐Ensure you are alligned with this North Star objective*⭐
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
[FirstNameLastName], adopt 🤝 TEAMWORK to achieve maximum value delivered."
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
*Remember:*
|
|
77
|
-
An agent has no inherit knowledge of previous batch agents. They can only collaborate within thier batch.
|
|
78
|
-
Don't refer to prior batch agents by name. Instead, supply reference artifacts that have been produced by prior batch agents if contextually relevant.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
### Batch Composition Goals
|
|
82
|
-
- **Minimum**: 5 agents per batch
|
|
83
|
-
- **Target**: 8-10 agents including support roles
|
|
84
|
-
- **Include**: roughly equal number of gemini, codex, and engineer implementers, and a support architect for guidance/coordination.
|
|
85
|
-
- **Follow with**: Verification batch always
|
|
86
|
-
|
|
87
|
-
### Phase Management
|
|
88
|
-
**New Feature Indicators**: Major functionality, no thread context
|
|
89
|
-
→ Create phase-id: `XX-DescriptiveName` (e.g., `03-UserAuth`)
|
|
90
|
-
|
|
91
|
-
**Continuation Indicators**: Bug fix, refinement, existing thread
|
|
92
|
-
→ Use existing phase or project-level(for project level scope)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
## Consultant Agent
|
|
96
|
-
Consultants come in two variants: Codex and Gemini. leverage them both liberally to diversify.
|
|
97
|
-
For implementation batches, distribute the tasks across engineers, codex consultants, and gemini consultants. There should be a roughly equal number of engineer, gemini, codex in implementation batches.
|
|
98
|
-
When you are tasking an agent with read+document type task, recruit a codex and gemini Consultant with the exact same instructions (except a different names). Include the consultants in the same batch. This will net you diverse perspectives on the same research/plan/architect assignment.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
## 🔴 FINAL DIRECTIVES
|
|
104
|
-
|
|
105
|
-
**DO NOT STOP** until assignment is complete and verified
|
|
106
|
-
|
|
107
|
-
**Think about dependencies** - logical, functional, file, verification, knowledge
|
|
108
|
-
|
|
109
|
-
**Maximize parallelization** - more agents, clear file ownership
|
|
110
|
-
|
|
111
|
-
**Phase-id format**: `XX-DescriptiveName` for new features
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## 🚨 CRITICAL: Response Protocol
|
|
116
|
-
|
|
117
|
-
### NEVER respond with status updates
|
|
118
|
-
### ONLY respond when assignment is COMPLETE and VERIFIED
|
|
119
|
-
### Continue orchestrating batches until DONE
|
|
120
|
-
|
|
121
|
-
**Begin orchestrating this Assignment NOW!**
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import sys
|
|
5
|
-
import requests
|
|
6
|
-
import argparse
|
|
7
|
-
import os
|
|
8
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
9
|
-
from utils.server_config import get_server_url
|
|
10
|
-
|
|
11
|
-
def get_unread_messages(subagent_name):
|
|
12
|
-
"""
|
|
13
|
-
Get unread messages for a specific subagent.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
subagent_name: The name/nickname of the subagent requesting messages
|
|
17
|
-
|
|
18
|
-
Returns:
|
|
19
|
-
List of unread messages with sender, message, and timestamp
|
|
20
|
-
"""
|
|
21
|
-
try:
|
|
22
|
-
response = requests.post(
|
|
23
|
-
f'{get_server_url()}/subagents/unread',
|
|
24
|
-
json={
|
|
25
|
-
'subagent_name': subagent_name
|
|
26
|
-
},
|
|
27
|
-
timeout=5
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
if response.status_code == 200:
|
|
31
|
-
data = response.json()
|
|
32
|
-
return data.get('messages', [])
|
|
33
|
-
else:
|
|
34
|
-
print(f"Error: Server returned status {response.status_code}", file=sys.stderr)
|
|
35
|
-
return []
|
|
36
|
-
|
|
37
|
-
except requests.exceptions.ConnectionError:
|
|
38
|
-
print(f"Error: Could not connect to observability server at {get_server_url()}", file=sys.stderr)
|
|
39
|
-
return []
|
|
40
|
-
except requests.exceptions.Timeout:
|
|
41
|
-
print("Error: Request timed out", file=sys.stderr)
|
|
42
|
-
return []
|
|
43
|
-
except Exception as e:
|
|
44
|
-
print(f"Error: {str(e)}", file=sys.stderr)
|
|
45
|
-
return []
|
|
46
|
-
|
|
47
|
-
def main():
|
|
48
|
-
parser = argparse.ArgumentParser(description='Get unread messages for a subagent')
|
|
49
|
-
parser.add_argument('--name', required=True, help='Subagent name/nickname')
|
|
50
|
-
parser.add_argument('--json', action='store_true', help='Output as JSON')
|
|
51
|
-
|
|
52
|
-
args = parser.parse_args()
|
|
53
|
-
|
|
54
|
-
messages = get_unread_messages(args.name)
|
|
55
|
-
|
|
56
|
-
if args.json:
|
|
57
|
-
print(json.dumps(messages, indent=2))
|
|
58
|
-
else:
|
|
59
|
-
if not messages:
|
|
60
|
-
print("No unread messages")
|
|
61
|
-
else:
|
|
62
|
-
print(f"Found {len(messages)} unread message(s):")
|
|
63
|
-
print("-" * 50)
|
|
64
|
-
for msg in messages:
|
|
65
|
-
print(f"From: {msg['sender']}")
|
|
66
|
-
print(f"Time: {msg['created_at']}")
|
|
67
|
-
print(f"Message: {json.dumps(msg['message'], indent=2)}")
|
|
68
|
-
print("-" * 50)
|
|
69
|
-
|
|
70
|
-
if __name__ == '__main__':
|
|
71
|
-
main()
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S uv run --script
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.8"
|
|
4
|
-
# dependencies = [
|
|
5
|
-
# "requests",
|
|
6
|
-
# ]
|
|
7
|
-
# ///
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import sys
|
|
11
|
-
import requests
|
|
12
|
-
import os
|
|
13
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
14
|
-
from utils.server_config import get_server_url
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def main():
|
|
18
|
-
try:
|
|
19
|
-
# Read JSON input from stdin
|
|
20
|
-
input_data = json.load(sys.stdin)
|
|
21
|
-
|
|
22
|
-
session_id = input_data.get('session_id', 'unknown')
|
|
23
|
-
|
|
24
|
-
# Check if this is a Task tool use and register the subagent
|
|
25
|
-
tool_name = input_data.get('tool_name', '')
|
|
26
|
-
tool_input = input_data.get('tool_input', {})
|
|
27
|
-
|
|
28
|
-
if tool_name == 'Task':
|
|
29
|
-
description = tool_input.get('description', '')
|
|
30
|
-
subagent_type = tool_input.get('subagent_type', '')
|
|
31
|
-
initial_prompt = tool_input.get('prompt', '')
|
|
32
|
-
|
|
33
|
-
# Extract nickname from description (part before colon)
|
|
34
|
-
if ':' in description:
|
|
35
|
-
nickname = description.split(':')[0].strip()
|
|
36
|
-
|
|
37
|
-
# Register the subagent with the server
|
|
38
|
-
try:
|
|
39
|
-
response = requests.post(
|
|
40
|
-
f'{get_server_url()}/subagents/register',
|
|
41
|
-
json={
|
|
42
|
-
'session_id': session_id,
|
|
43
|
-
'name': nickname,
|
|
44
|
-
'subagent_type': subagent_type
|
|
45
|
-
},
|
|
46
|
-
timeout=2
|
|
47
|
-
)
|
|
48
|
-
if response.status_code == 200:
|
|
49
|
-
print(f"Registered subagent: {nickname} ({subagent_type})", file=sys.stderr)
|
|
50
|
-
|
|
51
|
-
# Now update with initial prompt using PATCH endpoint
|
|
52
|
-
if initial_prompt:
|
|
53
|
-
prompt_response = requests.patch(
|
|
54
|
-
f'{get_server_url()}/subagents/{session_id}/{nickname}',
|
|
55
|
-
json={'initial_prompt': initial_prompt},
|
|
56
|
-
timeout=2
|
|
57
|
-
)
|
|
58
|
-
if prompt_response.status_code == 200:
|
|
59
|
-
print(f"Stored initial prompt for subagent: {nickname}", file=sys.stderr)
|
|
60
|
-
except Exception as e:
|
|
61
|
-
# Silently fail if server is not available
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
|
-
sys.exit(0)
|
|
65
|
-
|
|
66
|
-
except json.JSONDecodeError:
|
|
67
|
-
# Gracefully handle JSON decode errors
|
|
68
|
-
sys.exit(0)
|
|
69
|
-
except Exception:
|
|
70
|
-
# Handle any other errors gracefully
|
|
71
|
-
sys.exit(0)
|
|
72
|
-
|
|
73
|
-
if __name__ == '__main__':
|
|
74
|
-
main()
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import sys
|
|
5
|
-
import requests
|
|
6
|
-
import argparse
|
|
7
|
-
import os
|
|
8
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
9
|
-
from utils.server_config import get_server_url
|
|
10
|
-
|
|
11
|
-
def send_message(sender_name, message):
|
|
12
|
-
"""
|
|
13
|
-
Send a message from a subagent to all other subagents.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
sender_name: The name/nickname of the sending subagent
|
|
17
|
-
message: The message to send (can be string or dict)
|
|
18
|
-
|
|
19
|
-
Returns:
|
|
20
|
-
True if message was sent successfully, False otherwise
|
|
21
|
-
"""
|
|
22
|
-
try:
|
|
23
|
-
# Parse message if it's a JSON string
|
|
24
|
-
if isinstance(message, str):
|
|
25
|
-
try:
|
|
26
|
-
message = json.loads(message)
|
|
27
|
-
except json.JSONDecodeError:
|
|
28
|
-
# Keep as string if not valid JSON
|
|
29
|
-
pass
|
|
30
|
-
|
|
31
|
-
response = requests.post(
|
|
32
|
-
f'{get_server_url()}/subagents/message',
|
|
33
|
-
json={
|
|
34
|
-
'sender': sender_name,
|
|
35
|
-
'message': message
|
|
36
|
-
},
|
|
37
|
-
timeout=5
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
if response.status_code == 200:
|
|
41
|
-
return True
|
|
42
|
-
else:
|
|
43
|
-
print(f"Error: Server returned status {response.status_code}", file=sys.stderr)
|
|
44
|
-
return False
|
|
45
|
-
|
|
46
|
-
except requests.exceptions.ConnectionError:
|
|
47
|
-
print(f"Error: Could not connect to observability server at {get_server_url()}", file=sys.stderr)
|
|
48
|
-
return False
|
|
49
|
-
except requests.exceptions.Timeout:
|
|
50
|
-
print("Error: Request timed out", file=sys.stderr)
|
|
51
|
-
return False
|
|
52
|
-
except Exception as e:
|
|
53
|
-
print(f"Error: {str(e)}", file=sys.stderr)
|
|
54
|
-
return False
|
|
55
|
-
|
|
56
|
-
def main():
|
|
57
|
-
parser = argparse.ArgumentParser(description='Send a message from a subagent')
|
|
58
|
-
parser.add_argument('--sender', required=True, help='Sender subagent name/nickname')
|
|
59
|
-
parser.add_argument('--message', required=True, help='Message to send (string or JSON)')
|
|
60
|
-
parser.add_argument('--type', help='Message type (optional)')
|
|
61
|
-
parser.add_argument('--data', help='Additional data (JSON format)')
|
|
62
|
-
|
|
63
|
-
args = parser.parse_args()
|
|
64
|
-
|
|
65
|
-
# Build message structure if type is provided
|
|
66
|
-
if args.type:
|
|
67
|
-
message = {
|
|
68
|
-
'type': args.type,
|
|
69
|
-
'content': args.message
|
|
70
|
-
}
|
|
71
|
-
if args.data:
|
|
72
|
-
try:
|
|
73
|
-
message['data'] = json.loads(args.data)
|
|
74
|
-
except json.JSONDecodeError:
|
|
75
|
-
message['data'] = args.data
|
|
76
|
-
else:
|
|
77
|
-
message = args.message
|
|
78
|
-
|
|
79
|
-
success = send_message(args.sender, message)
|
|
80
|
-
|
|
81
|
-
if success:
|
|
82
|
-
print("Message sent successfully")
|
|
83
|
-
else:
|
|
84
|
-
sys.exit(1)
|
|
85
|
-
|
|
86
|
-
if __name__ == '__main__':
|
|
87
|
-
main()
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S uv run --script
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.8"
|
|
4
|
-
# dependencies = [
|
|
5
|
-
# "requests",
|
|
6
|
-
# ]
|
|
7
|
-
# ///
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import sys
|
|
11
|
-
import requests
|
|
12
|
-
import os
|
|
13
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
14
|
-
from utils.server_config import get_server_url
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def main():
|
|
18
|
-
try:
|
|
19
|
-
# Read JSON input from stdin
|
|
20
|
-
input_data = json.load(sys.stdin)
|
|
21
|
-
|
|
22
|
-
session_id = input_data.get('session_id', 'unknown')
|
|
23
|
-
|
|
24
|
-
# Check if this is a Task tool use completion
|
|
25
|
-
tool_name = input_data.get('tool_name', '')
|
|
26
|
-
tool_input = input_data.get('tool_input', {})
|
|
27
|
-
tool_response = input_data.get('tool_response', {})
|
|
28
|
-
|
|
29
|
-
if tool_name == 'Task':
|
|
30
|
-
description = tool_input.get('description', '')
|
|
31
|
-
|
|
32
|
-
# Extract agent name from description (part before colon)
|
|
33
|
-
if ':' in description:
|
|
34
|
-
agent_name = description.split(':')[0].strip()
|
|
35
|
-
|
|
36
|
-
# Extract completion metadata from tool_response
|
|
37
|
-
completion_metadata = {}
|
|
38
|
-
|
|
39
|
-
# Get duration, tokens, and tool use count from tool_response
|
|
40
|
-
if 'totalDurationMs' in tool_response:
|
|
41
|
-
completion_metadata['total_duration_ms'] = tool_response['totalDurationMs']
|
|
42
|
-
|
|
43
|
-
if 'totalTokens' in tool_response:
|
|
44
|
-
completion_metadata['total_tokens'] = tool_response['totalTokens']
|
|
45
|
-
|
|
46
|
-
if 'totalToolUseCount' in tool_response:
|
|
47
|
-
completion_metadata['total_tool_use_count'] = tool_response['totalToolUseCount']
|
|
48
|
-
|
|
49
|
-
# Get usage details if available
|
|
50
|
-
usage = tool_response.get('usage', {})
|
|
51
|
-
if usage:
|
|
52
|
-
if 'input_tokens' in usage:
|
|
53
|
-
completion_metadata['input_tokens'] = usage['input_tokens']
|
|
54
|
-
|
|
55
|
-
if 'output_tokens' in usage:
|
|
56
|
-
completion_metadata['output_tokens'] = usage['output_tokens']
|
|
57
|
-
|
|
58
|
-
if 'cache_creation_input_tokens' in usage:
|
|
59
|
-
completion_metadata['cache_creation_input_tokens'] = usage['cache_creation_input_tokens']
|
|
60
|
-
|
|
61
|
-
if 'cache_read_input_tokens' in usage:
|
|
62
|
-
completion_metadata['cache_read_input_tokens'] = usage['cache_read_input_tokens']
|
|
63
|
-
|
|
64
|
-
# Extract the agent's final response content
|
|
65
|
-
# The response content is typically in the main tool_response as text
|
|
66
|
-
final_response = ''
|
|
67
|
-
if 'content' in tool_response:
|
|
68
|
-
# If content is a list (standard Claude format)
|
|
69
|
-
if isinstance(tool_response['content'], list):
|
|
70
|
-
text_blocks = [block.get('text', '') for block in tool_response['content'] if block.get('type') == 'text']
|
|
71
|
-
final_response = '\n'.join(text_blocks)
|
|
72
|
-
# If content is a string
|
|
73
|
-
elif isinstance(tool_response['content'], str):
|
|
74
|
-
final_response = tool_response['content']
|
|
75
|
-
elif 'text' in tool_response:
|
|
76
|
-
# Alternative field name
|
|
77
|
-
final_response = tool_response['text']
|
|
78
|
-
elif 'response' in tool_response:
|
|
79
|
-
# Another possible field name
|
|
80
|
-
final_response = tool_response['response']
|
|
81
|
-
|
|
82
|
-
# Extract tool calls if available
|
|
83
|
-
tool_calls_data = []
|
|
84
|
-
if 'tool_calls' in tool_response:
|
|
85
|
-
tool_calls_data = tool_response['tool_calls']
|
|
86
|
-
elif 'toolCalls' in tool_response:
|
|
87
|
-
tool_calls_data = tool_response['toolCalls']
|
|
88
|
-
|
|
89
|
-
# Update subagent completion status
|
|
90
|
-
try:
|
|
91
|
-
response = requests.post(
|
|
92
|
-
f'{get_server_url()}/subagents/update-completion',
|
|
93
|
-
json={
|
|
94
|
-
'session_id': session_id,
|
|
95
|
-
'name': agent_name,
|
|
96
|
-
'status': 'completed',
|
|
97
|
-
'completed_at': None, # Will be set to current time by server
|
|
98
|
-
'completion_metadata': completion_metadata,
|
|
99
|
-
'final_response': final_response,
|
|
100
|
-
'tool_calls': tool_calls_data
|
|
101
|
-
},
|
|
102
|
-
timeout=2
|
|
103
|
-
)
|
|
104
|
-
if response.status_code == 200:
|
|
105
|
-
print(f"Updated completion for subagent: {agent_name}", file=sys.stderr)
|
|
106
|
-
elif response.status_code == 404:
|
|
107
|
-
# Agent not found, silently ignore (might be a direct Task not created by subagent)
|
|
108
|
-
pass
|
|
109
|
-
else:
|
|
110
|
-
# Other error, log but don't fail
|
|
111
|
-
print(f"Failed to update completion for {agent_name}: {response.status_code}", file=sys.stderr)
|
|
112
|
-
except Exception as e:
|
|
113
|
-
# Silently fail if server is not available
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
sys.exit(0)
|
|
117
|
-
|
|
118
|
-
except json.JSONDecodeError:
|
|
119
|
-
# Gracefully handle JSON decode errors
|
|
120
|
-
sys.exit(0)
|
|
121
|
-
except Exception:
|
|
122
|
-
# Handle any other errors gracefully
|
|
123
|
-
sys.exit(0)
|
|
124
|
-
|
|
125
|
-
if __name__ == '__main__':
|
|
126
|
-
main()
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# UserPromptSubmit hook to inject context into the conversation
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import sys
|
|
6
|
-
# NOTE: this ONLY works for userPromptSubmit hook, no other hooks are supported.
|
|
7
|
-
def main():
|
|
8
|
-
try:
|
|
9
|
-
# Read JSON input from stdin
|
|
10
|
-
input_data = json.load(sys.stdin)
|
|
11
|
-
|
|
12
|
-
# Extract the user's prompt
|
|
13
|
-
|
|
14
|
-
session_id = input_data.get('session_id', 'unknown')
|
|
15
|
-
|
|
16
|
-
# Create additional context that will be injected
|
|
17
|
-
additional_context = "Your session_id is: {session_id}"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
output = {
|
|
21
|
-
"hookSpecificOutput": {
|
|
22
|
-
"hookEventName": "UserPromptSubmit",
|
|
23
|
-
"additionalContext": additional_context
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
print(json.dumps(output))
|
|
27
|
-
sys.exit(0)
|
|
28
|
-
|
|
29
|
-
except Exception as e:
|
|
30
|
-
# On error, show to user but don't block
|
|
31
|
-
print(f"Error in UserPromptSubmit hook: {e}", file=sys.stderr)
|
|
32
|
-
sys.exit(1)
|
|
33
|
-
|
|
34
|
-
if __name__ == '__main__':
|
|
35
|
-
main()
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S uv run --script
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.8"
|
|
4
|
-
# ///
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
Multi-Agent Observability Hook Script
|
|
8
|
-
Sends Claude Code hook events to the observability server.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import sys
|
|
13
|
-
import os
|
|
14
|
-
import argparse
|
|
15
|
-
import urllib.request
|
|
16
|
-
import urllib.error
|
|
17
|
-
from datetime import datetime
|
|
18
|
-
|
|
19
|
-
# Add parent directory to path for imports
|
|
20
|
-
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
21
|
-
from utils.server_config import get_server_url
|
|
22
|
-
|
|
23
|
-
def send_event_to_server(event_data, server_url=None):
|
|
24
|
-
"""Send event data to the observability server."""
|
|
25
|
-
if server_url is None:
|
|
26
|
-
server_url = f'{get_server_url()}/events'
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
# Prepare the request
|
|
30
|
-
req = urllib.request.Request(
|
|
31
|
-
server_url,
|
|
32
|
-
data=json.dumps(event_data).encode('utf-8'),
|
|
33
|
-
headers={
|
|
34
|
-
'Content-Type': 'application/json',
|
|
35
|
-
'User-Agent': 'Claude-Code-Hook/1.0'
|
|
36
|
-
}
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
# Send the request
|
|
40
|
-
with urllib.request.urlopen(req, timeout=5) as response:
|
|
41
|
-
if response.status == 200:
|
|
42
|
-
return True
|
|
43
|
-
else:
|
|
44
|
-
print(f"Server returned status: {response.status}", file=sys.stderr)
|
|
45
|
-
return False
|
|
46
|
-
|
|
47
|
-
except urllib.error.URLError as e:
|
|
48
|
-
print(f"Failed to send event: {e}", file=sys.stderr)
|
|
49
|
-
return False
|
|
50
|
-
except Exception as e:
|
|
51
|
-
print(f"Unexpected error: {e}", file=sys.stderr)
|
|
52
|
-
return False
|
|
53
|
-
|
|
54
|
-
def main():
|
|
55
|
-
# Parse command line arguments
|
|
56
|
-
parser = argparse.ArgumentParser(description='Send Claude Code hook events to observability server')
|
|
57
|
-
parser.add_argument('--source-app', required=True, help='Source application name')
|
|
58
|
-
parser.add_argument('--event-type', required=True, help='Hook event type (PreToolUse, PostToolUse, etc.)')
|
|
59
|
-
parser.add_argument('--add-chat', action='store_true', help='Include chat transcript if available')
|
|
60
|
-
parser.add_argument('--summarize', action='store_true', help='Generate AI summary of the event')
|
|
61
|
-
|
|
62
|
-
args = parser.parse_args()
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
# Read hook data from stdin
|
|
66
|
-
input_data = json.load(sys.stdin)
|
|
67
|
-
except json.JSONDecodeError as e:
|
|
68
|
-
print(f"Failed to parse JSON input: {e}", file=sys.stderr)
|
|
69
|
-
sys.exit(1)
|
|
70
|
-
|
|
71
|
-
# Prepare event data for server
|
|
72
|
-
event_data = {
|
|
73
|
-
'source_app': args.source_app,
|
|
74
|
-
'session_id': input_data.get('session_id', 'unknown'),
|
|
75
|
-
'hook_event_type': args.event_type,
|
|
76
|
-
'payload': input_data,
|
|
77
|
-
'timestamp': int(datetime.now().timestamp() * 1000)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# Handle --add-chat option
|
|
81
|
-
if args.add_chat and 'transcript_path' in input_data:
|
|
82
|
-
transcript_path = input_data['transcript_path']
|
|
83
|
-
if os.path.exists(transcript_path):
|
|
84
|
-
# Read .jsonl file and convert to JSON array
|
|
85
|
-
chat_data = []
|
|
86
|
-
try:
|
|
87
|
-
with open(transcript_path, 'r') as f:
|
|
88
|
-
for line in f:
|
|
89
|
-
line = line.strip()
|
|
90
|
-
if line:
|
|
91
|
-
try:
|
|
92
|
-
chat_data.append(json.loads(line))
|
|
93
|
-
except json.JSONDecodeError:
|
|
94
|
-
pass # Skip invalid lines
|
|
95
|
-
|
|
96
|
-
# Add chat to event data
|
|
97
|
-
event_data['chat'] = chat_data
|
|
98
|
-
except Exception as e:
|
|
99
|
-
print(f"Failed to read transcript: {e}", file=sys.stderr)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# Send to server
|
|
103
|
-
server_url = f'{get_server_url()}/events'
|
|
104
|
-
success = send_event_to_server(event_data, server_url)
|
|
105
|
-
|
|
106
|
-
# Always exit with 0 to not block Claude Code operations
|
|
107
|
-
sys.exit(0)
|
|
108
|
-
|
|
109
|
-
if __name__ == '__main__':
|
|
110
|
-
main()
|