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.
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "agent-relay",
3
+ "description": "Client connector for Agent Relay — auto-registers Claude Code sessions as agents and enables inter-agent messaging via a lightweight HTTP message bus",
4
+ "version": "0.1.0"
5
+ }
@@ -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
+ }