agent-relay-plugin 0.1.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/.claude-plugin/plugin.json +5 -0
- package/hooks/hooks.json +26 -0
- package/hooks/poll-inbox.sh +54 -0
- package/hooks/session-end.sh +27 -0
- package/hooks/session-start.sh +102 -0
- package/package.json +24 -0
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh\"",
|
|
9
|
+
"timeout": 15
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"SessionEnd": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-end.sh\"",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Agent Relay plugin — inbox poller
|
|
3
|
+
# Each new message prints one stdout line (consumed by Monitor as a notification).
|
|
4
|
+
# Usage: poll-inbox.sh <relay-url> <agent-id> [interval-seconds]
|
|
5
|
+
|
|
6
|
+
RELAY_URL="$1"
|
|
7
|
+
AGENT_ID="$2"
|
|
8
|
+
INTERVAL="${3:-10}"
|
|
9
|
+
|
|
10
|
+
if [ -z "$RELAY_URL" ] || [ -z "$AGENT_ID" ]; then
|
|
11
|
+
echo "Usage: poll-inbox.sh <relay-url> <agent-id> [interval]" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Bootstrap cursor at current max so we only see messages arriving after session start.
|
|
16
|
+
since_id=$(curl -s "${RELAY_URL}/api/messages/cursor" 2>/dev/null | jq -r '.latestId // 0' 2>/dev/null || echo 0)
|
|
17
|
+
|
|
18
|
+
fail_streak=0
|
|
19
|
+
while true; do
|
|
20
|
+
hb_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
|
|
21
|
+
"${RELAY_URL}/api/agents/${AGENT_ID}/heartbeat" 2>/dev/null)
|
|
22
|
+
|
|
23
|
+
msgs=$(curl -s --fail "${RELAY_URL}/api/messages?for=${AGENT_ID}&sinceId=${since_id}&unread=true" 2>/dev/null)
|
|
24
|
+
poll_rc=$?
|
|
25
|
+
|
|
26
|
+
if [ "$hb_code" != "200" ] || [ $poll_rc -ne 0 ]; then
|
|
27
|
+
fail_streak=$((fail_streak + 1))
|
|
28
|
+
case "$fail_streak" in
|
|
29
|
+
1) delay=2 ;;
|
|
30
|
+
2) delay=10 ;;
|
|
31
|
+
3) delay=30 ;;
|
|
32
|
+
*) delay=60 ;;
|
|
33
|
+
esac
|
|
34
|
+
if [ "$fail_streak" = "1" ]; then
|
|
35
|
+
echo "warn: agent-relay unreachable (${RELAY_URL}) — backing off" >&2
|
|
36
|
+
fi
|
|
37
|
+
sleep "$delay"
|
|
38
|
+
continue
|
|
39
|
+
fi
|
|
40
|
+
fail_streak=0
|
|
41
|
+
|
|
42
|
+
count=$(echo "$msgs" | jq 'length' 2>/dev/null || echo 0)
|
|
43
|
+
if [ "$count" -gt 0 ] 2>/dev/null && [ "$count" != "0" ]; then
|
|
44
|
+
echo "$msgs" | jq -r '.[] | "\(.from) → \(.to) | \(.subject // "(no subject)"): \(.body)"'
|
|
45
|
+
since_id=$(echo "$msgs" | jq '[.[].id] | max')
|
|
46
|
+
echo "$msgs" | jq -r '.[].id' | while read -r mid; do
|
|
47
|
+
curl -s -X PATCH "${RELAY_URL}/api/messages/${mid}" \
|
|
48
|
+
-H 'Content-Type: application/json' \
|
|
49
|
+
-d "{\"readBy\":\"${AGENT_ID}\"}" > /dev/null 2>&1 || true
|
|
50
|
+
done
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
sleep "$INTERVAL"
|
|
54
|
+
done
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Agent Relay plugin — SessionEnd hook
|
|
3
|
+
# Marks this session's agent as offline.
|
|
4
|
+
|
|
5
|
+
RELAY_URL="${AGENT_RELAY_URL:-http://localhost:4850}"
|
|
6
|
+
|
|
7
|
+
input=$(cat)
|
|
8
|
+
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
9
|
+
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
10
|
+
|
|
11
|
+
machine=$(hostname)
|
|
12
|
+
project=$(basename "${cwd:-unknown}")
|
|
13
|
+
rig="${CLAUDE_RIG_NAME:-default}"
|
|
14
|
+
|
|
15
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
16
|
+
short_sid=$(printf '%s' "$session_id" | shasum -a 1 | head -c 6)
|
|
17
|
+
else
|
|
18
|
+
short_sid=$(printf '%s' "$session_id" | md5sum | head -c 6)
|
|
19
|
+
fi
|
|
20
|
+
agent_id="${machine}-${rig}-${project}-${short_sid}"
|
|
21
|
+
|
|
22
|
+
curl -s -X PATCH "${RELAY_URL}/api/agents/${agent_id}/status" \
|
|
23
|
+
-H 'Content-Type: application/json' \
|
|
24
|
+
-d '{"status":"offline"}' \
|
|
25
|
+
> /dev/null 2>&1 &
|
|
26
|
+
|
|
27
|
+
exit 0
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Agent Relay plugin — SessionStart hook
|
|
3
|
+
# Registers this Claude Code session as an agent and injects messaging context.
|
|
4
|
+
|
|
5
|
+
RELAY_URL="${AGENT_RELAY_URL:-http://localhost:4850}"
|
|
6
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
|
|
7
|
+
|
|
8
|
+
input=$(cat)
|
|
9
|
+
|
|
10
|
+
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
11
|
+
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
12
|
+
model=$(echo "$input" | jq -r '.model // empty')
|
|
13
|
+
source=$(echo "$input" | jq -r '.source // empty')
|
|
14
|
+
|
|
15
|
+
machine=$(hostname)
|
|
16
|
+
project=$(basename "${cwd:-unknown}")
|
|
17
|
+
rig="${CLAUDE_RIG_NAME:-default}"
|
|
18
|
+
|
|
19
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
20
|
+
short_sid=$(printf '%s' "$session_id" | shasum -a 1 | head -c 6)
|
|
21
|
+
else
|
|
22
|
+
short_sid=$(printf '%s' "$session_id" | md5sum | head -c 6)
|
|
23
|
+
fi
|
|
24
|
+
agent_id="${machine}-${rig}-${project}-${short_sid}"
|
|
25
|
+
|
|
26
|
+
# Capabilities: AGENT_RELAY_CAPS="chat,review,edit" (comma-separated) → JSON array
|
|
27
|
+
caps_csv="${AGENT_RELAY_CAPS:-chat}"
|
|
28
|
+
caps_json=$(echo "$caps_csv" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
|
|
29
|
+
|
|
30
|
+
# Register agent card
|
|
31
|
+
reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agents" \
|
|
32
|
+
-H 'Content-Type: application/json' \
|
|
33
|
+
-d "$(jq -n \
|
|
34
|
+
--arg id "$agent_id" \
|
|
35
|
+
--arg name "$project ($rig @ $machine)" \
|
|
36
|
+
--arg machine "$machine" \
|
|
37
|
+
--arg rig "$rig" \
|
|
38
|
+
--argjson tags "$(jq -n --arg r "$rig" --arg p "$project" '[$r, $p]')" \
|
|
39
|
+
--argjson caps "$caps_json" \
|
|
40
|
+
--argjson meta "$(jq -n --arg sid "$session_id" --arg model "$model" --arg src "$source" --arg cwd "$cwd" '{sessionId: $sid, model: $model, source: $src, cwd: $cwd}')" \
|
|
41
|
+
'{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta}'
|
|
42
|
+
)" 2>/dev/null)
|
|
43
|
+
if [ "$reg_code" != "201" ] && [ "$reg_code" != "200" ]; then
|
|
44
|
+
echo "warn: agent-relay register failed (HTTP ${reg_code:-none}) — ${RELAY_URL}" >&2
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Check for unread messages
|
|
48
|
+
unread=$(curl -s "${RELAY_URL}/api/messages?for=${agent_id}&unread=true" 2>/dev/null)
|
|
49
|
+
unread_count=$(echo "$unread" | jq 'length' 2>/dev/null || echo "0")
|
|
50
|
+
|
|
51
|
+
# Build context output
|
|
52
|
+
cat <<CONTEXT
|
|
53
|
+
Agent Relay active. Your agent ID: ${agent_id}
|
|
54
|
+
Relay URL: ${RELAY_URL}
|
|
55
|
+
|
|
56
|
+
To send a message:
|
|
57
|
+
curl -s -X POST ${RELAY_URL}/api/messages -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"TARGET","body":"MESSAGE"}'
|
|
58
|
+
TARGET: agent-id (direct), tag:NAME (by tag), cap:NAME (by capability), label:NAME (by human-set label, fan-out), broadcast (all)
|
|
59
|
+
|
|
60
|
+
To rename yourself (set a short, human-friendly label so the user can refer to you easily):
|
|
61
|
+
curl -s -X PATCH ${RELAY_URL}/api/agents/${agent_id}/label -H 'Content-Type: application/json' -d '{"label":"backend fixing"}'
|
|
62
|
+
When the user tells you to rename yourself ("call yourself X", "rename to X"), call this endpoint.
|
|
63
|
+
Labels are NOT unique — if multiple sessions share a label, \`to: "label:X"\` fans out to all of them.
|
|
64
|
+
Use \`to: "label:backend fixing"\` when the user says "tell 'backend fixing' to do Y".
|
|
65
|
+
|
|
66
|
+
To reply to a message (threading):
|
|
67
|
+
curl -s -X POST ${RELAY_URL}/api/messages -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"TARGET","body":"MESSAGE","replyTo":MSG_ID}'
|
|
68
|
+
Always use replyTo when responding to a specific message — it creates a thread.
|
|
69
|
+
|
|
70
|
+
To send a claimable task (only one agent can claim it):
|
|
71
|
+
curl -s -X POST ${RELAY_URL}/api/messages -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"cap:review","body":"TASK","claimable":true}'
|
|
72
|
+
|
|
73
|
+
To claim a task:
|
|
74
|
+
curl -s -X POST ${RELAY_URL}/api/messages/MSG_ID/claim -H 'Content-Type: application/json' -d '{"agentId":"${agent_id}"}'
|
|
75
|
+
When you see a claimable message, claim it BEFORE starting work. If claim fails, someone else got it — move on.
|
|
76
|
+
|
|
77
|
+
To view a thread:
|
|
78
|
+
curl -s '${RELAY_URL}/api/messages/MSG_ID/thread'
|
|
79
|
+
|
|
80
|
+
To check messages:
|
|
81
|
+
curl -s '${RELAY_URL}/api/messages?for=${agent_id}&unread=true'
|
|
82
|
+
|
|
83
|
+
To mark read:
|
|
84
|
+
curl -s -X PATCH ${RELAY_URL}/api/messages/ID -H 'Content-Type: application/json' -d '{"readBy":"${agent_id}"}'
|
|
85
|
+
|
|
86
|
+
To find agents by capability:
|
|
87
|
+
curl -s '${RELAY_URL}/api/agents/find?capability=review'
|
|
88
|
+
|
|
89
|
+
To list online agents:
|
|
90
|
+
curl -s '${RELAY_URL}/api/agents?status=online'
|
|
91
|
+
|
|
92
|
+
IMPORTANT: Start a persistent Monitor NOW to listen for incoming messages:
|
|
93
|
+
Monitor({ command: "${PLUGIN_ROOT}/hooks/poll-inbox.sh ${RELAY_URL} ${agent_id} 10", description: "Agent Relay inbox for ${agent_id}", persistent: true })
|
|
94
|
+
You MUST start this Monitor immediately at session start. Do not wait for the user to ask.
|
|
95
|
+
CONTEXT
|
|
96
|
+
|
|
97
|
+
# Append unread messages if any
|
|
98
|
+
if [ "$unread_count" -gt 0 ] 2>/dev/null && [ "$unread_count" != "0" ]; then
|
|
99
|
+
echo ""
|
|
100
|
+
echo "${unread_count} unread message(s):"
|
|
101
|
+
echo "$unread" | jq -r '.[] | " [\(.from)] \(.subject // "(no subject)"): \(.body | if length > 120 then .[:120] + "..." else . end)"'
|
|
102
|
+
fi
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-relay-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Claude Code plugin for Agent Relay — auto-registers sessions as agents and enables inter-agent messaging",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Edin Mujkanovic <edin@exelerus.com>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/edimuj/agent-relay.git",
|
|
11
|
+
"directory": "plugin"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
".claude-plugin/",
|
|
15
|
+
"hooks/"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude-code",
|
|
19
|
+
"claude-code-plugin",
|
|
20
|
+
"agent-relay",
|
|
21
|
+
"multi-agent",
|
|
22
|
+
"inter-agent-communication"
|
|
23
|
+
]
|
|
24
|
+
}
|