agent-relay-plugin 0.1.1 → 0.2.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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "agent-relay",
3
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.1"
4
+ "version": "0.2.0"
5
5
  }
package/hooks/hooks.json CHANGED
@@ -1,16 +1,5 @@
1
1
  {
2
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
3
  "SessionEnd": [
15
4
  {
16
5
  "hooks": [
@@ -12,14 +12,26 @@ if [ -z "$RELAY_URL" ] || [ -z "$AGENT_ID" ]; then
12
12
  exit 1
13
13
  fi
14
14
 
15
+ # PID file so session-start.sh can find and kill us on /clear or /compact.
16
+ pid_file="/tmp/agent-relay-poll-${AGENT_ID}.pid"
17
+ cleanup() { rm -f "$pid_file"; }
18
+ trap cleanup EXIT
19
+ echo $$ > "$pid_file"
20
+
15
21
  # Bootstrap cursor at current max so we only see messages arriving after session start.
16
22
  since_id=$(curl -s "${RELAY_URL}/api/messages/cursor" 2>/dev/null | jq -r '.latestId // 0' 2>/dev/null || echo 0)
17
23
 
18
24
  fail_streak=0
25
+ marked_ready=false
19
26
  while true; do
20
27
  hb_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
21
28
  "${RELAY_URL}/api/agents/${AGENT_ID}/heartbeat" 2>/dev/null)
22
29
 
30
+ # Agent was deleted or doesn't exist — we're orphaned, exit.
31
+ if [ "$hb_code" = "404" ]; then
32
+ exit 0
33
+ fi
34
+
23
35
  msgs=$(curl -s --fail "${RELAY_URL}/api/messages?for=${AGENT_ID}&sinceId=${since_id}&unread=true" 2>/dev/null)
24
36
  poll_rc=$?
25
37
 
@@ -39,9 +51,15 @@ while true; do
39
51
  fi
40
52
  fail_streak=0
41
53
 
54
+ if [ "$marked_ready" = "false" ]; then
55
+ curl -s -o /dev/null -X PATCH "${RELAY_URL}/api/agents/${AGENT_ID}/ready" \
56
+ -H 'Content-Type: application/json' -d '{"ready":true}' 2>/dev/null
57
+ marked_ready=true
58
+ fi
59
+
42
60
  count=$(echo "$msgs" | jq 'length' 2>/dev/null || echo 0)
43
61
  if [ "$count" -gt 0 ] 2>/dev/null && [ "$count" != "0" ]; then
44
- echo "$msgs" | jq -r '.[] | "\(.from) → \(.to) | \(.subject // "(no subject)"): \(.body)"'
62
+ echo "$msgs" | jq -r '.[] | if .type == "system" then "⚠ SYSTEM [msg:\(.id)]: \(.body)" else "[msg:\(.id)] \(.from) → \(.to) | \(.subject // "(no subject)"): \(.body)" end'
45
63
  since_id=$(echo "$msgs" | jq '[.[].id] | max')
46
64
  echo "$msgs" | jq -r '.[].id' | while read -r mid; do
47
65
  curl -s -X PATCH "${RELAY_URL}/api/messages/${mid}" \
@@ -1,19 +1,16 @@
1
1
  #!/usr/bin/env bash
2
- # Agent Relay plugin SessionStart hook
3
- # Registers this Claude Code session as an agent and injects messaging context.
2
+ # Agent Relay — self-contained native monitor
3
+ # Registers this session as an agent, outputs messaging context, then polls inbox.
4
+ # Runs automatically via monitors.json — no user interaction required.
5
+ # PPID = Claude Code process, stable across /clear and /compact.
4
6
 
5
7
  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')
8
+ PLUGIN_VERSION=$(jq -r '.version' "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json" 2>/dev/null || echo "unknown")
14
9
 
10
+ # --- Compute agent identity ---
15
11
  machine=$(hostname)
16
- project=$(basename "${cwd:-unknown}")
12
+ project=$(basename "${PWD:-unknown}")
13
+
17
14
  if [ -n "$CLAUDE_RIG_NAME" ]; then
18
15
  rig="$CLAUDE_RIG_NAME"
19
16
  elif [ -n "$CLAUDE_CONFIG_DIR" ]; then
@@ -23,17 +20,35 @@ else
23
20
  fi
24
21
 
25
22
  if command -v shasum >/dev/null 2>&1; then
26
- short_sid=$(printf '%s' "$session_id" | shasum -a 1 | head -c 6)
23
+ short_pid=$(printf '%s' "$PPID" | shasum -a 1 | head -c 6)
27
24
  else
28
- short_sid=$(printf '%s' "$session_id" | md5sum | head -c 6)
25
+ short_pid=$(printf '%s' "$PPID" | md5sum | head -c 6)
26
+ fi
27
+ agent_id="${machine}-${rig}-${project}-${short_pid}"
28
+
29
+ # --- Clean up previous agent from this instance ---
30
+ instance_state="/tmp/agent-relay-instance-${PPID}.state"
31
+ if [ -f "$instance_state" ]; then
32
+ old_agent_id=$(head -1 "$instance_state" 2>/dev/null)
33
+ if [ -n "$old_agent_id" ] && [ "$old_agent_id" != "$agent_id" ]; then
34
+ old_pid_file="/tmp/agent-relay-poll-${old_agent_id}.pid"
35
+ if [ -f "$old_pid_file" ]; then
36
+ old_pid=$(cat "$old_pid_file" 2>/dev/null)
37
+ if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
38
+ kill "$old_pid" 2>/dev/null
39
+ fi
40
+ rm -f "$old_pid_file"
41
+ fi
42
+ curl -s -o /dev/null -X PATCH "${RELAY_URL}/api/agents/${old_agent_id}/status" \
43
+ -H 'Content-Type: application/json' -d '{"status":"offline"}' 2>/dev/null
44
+ fi
29
45
  fi
30
- agent_id="${machine}-${rig}-${project}-${short_sid}"
46
+ printf '%s\n' "$agent_id" > "$instance_state"
31
47
 
32
- # Capabilities: AGENT_RELAY_CAPS="chat,review,edit" (comma-separated) → JSON array
48
+ # --- Register agent ---
33
49
  caps_csv="${AGENT_RELAY_CAPS:-chat}"
34
50
  caps_json=$(echo "$caps_csv" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
35
51
 
36
- # Register agent card
37
52
  reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agents" \
38
53
  -H 'Content-Type: application/json' \
39
54
  -d "$(jq -n \
@@ -43,21 +58,27 @@ reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agen
43
58
  --arg rig "$rig" \
44
59
  --argjson tags "$(jq -n --arg r "$rig" --arg p "$project" '[$r, $p]')" \
45
60
  --argjson caps "$caps_json" \
46
- --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}')" \
61
+ --argjson meta "$(jq -n --arg cwd "$PWD" '{cwd: $cwd}')" \
47
62
  '{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta}'
48
63
  )" 2>/dev/null)
64
+
49
65
  if [ "$reg_code" != "201" ] && [ "$reg_code" != "200" ]; then
50
66
  echo "warn: agent-relay register failed (HTTP ${reg_code:-none}) — ${RELAY_URL}" >&2
51
67
  fi
52
68
 
53
- # Check for unread messages
54
- unread=$(curl -s "${RELAY_URL}/api/messages?for=${agent_id}&unread=true" 2>/dev/null)
55
- unread_count=$(echo "$unread" | jq 'length' 2>/dev/null || echo "0")
69
+ # --- Check server version ---
70
+ server_version=$(curl -s "${RELAY_URL}/api/stats" 2>/dev/null | jq -r '.version // empty' 2>/dev/null)
71
+ version_warning=""
72
+ if [ -z "$server_version" ]; then
73
+ version_warning="⚠ Server does not report its version — consider updating: bunx agent-relay-server@latest"
74
+ elif [ "$server_version" != "$PLUGIN_VERSION" ]; then
75
+ version_warning="⚠ Version mismatch — server ${server_version}, plugin ${PLUGIN_VERSION}. Update server: bunx agent-relay-server@latest"
76
+ fi
56
77
 
57
- # Build context output
78
+ # --- Output context (first stdout = notification injected into conversation) ---
58
79
  cat <<CONTEXT
59
80
  Agent Relay active. Your agent ID: ${agent_id}
60
- Relay URL: ${RELAY_URL}
81
+ Relay URL: ${RELAY_URL} | Server: ${server_version:-unknown} | Plugin: ${PLUGIN_VERSION}
61
82
 
62
83
  To send a message:
63
84
  curl -s -X POST ${RELAY_URL}/api/messages -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"TARGET","body":"MESSAGE"}'
@@ -72,6 +93,7 @@ To rename yourself (set a short, human-friendly label so the user can refer to y
72
93
  To reply to a message (threading):
73
94
  curl -s -X POST ${RELAY_URL}/api/messages -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"TARGET","body":"MESSAGE","replyTo":MSG_ID}'
74
95
  Always use replyTo when responding to a specific message — it creates a thread.
96
+ The inbox monitor prefixes each message with [msg:ID] — parse the ID from there.
75
97
 
76
98
  To send a claimable task (only one agent can claim it):
77
99
  curl -s -X POST ${RELAY_URL}/api/messages -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"cap:review","body":"TASK","claimable":true}'
@@ -95,14 +117,24 @@ To find agents by capability:
95
117
  To list online agents:
96
118
  curl -s '${RELAY_URL}/api/agents?status=online'
97
119
 
98
- IMPORTANT: Start a persistent Monitor NOW to listen for incoming messages:
99
- Monitor({ command: "${PLUGIN_ROOT}/hooks/poll-inbox.sh ${RELAY_URL} ${agent_id} 10", description: "Agent Relay inbox for ${agent_id}", persistent: true })
100
- You MUST start this Monitor immediately at session start. Do not wait for the user to ask.
120
+ System messages:
121
+ Messages prefixed with " SYSTEM" are relay-level instructions (e.g. plugin updates, restart requests).
122
+ When you see one, follow the instruction immediately for example, restart the inbox monitor if asked.
101
123
  CONTEXT
102
124
 
103
- # Append unread messages if any
125
+ if [ -n "$version_warning" ]; then
126
+ echo ""
127
+ echo "$version_warning"
128
+ fi
129
+
130
+ # --- Check for unread messages ---
131
+ unread=$(curl -s "${RELAY_URL}/api/messages?for=${agent_id}&unread=true" 2>/dev/null)
132
+ unread_count=$(echo "$unread" | jq 'length' 2>/dev/null || echo "0")
104
133
  if [ "$unread_count" -gt 0 ] 2>/dev/null && [ "$unread_count" != "0" ]; then
105
134
  echo ""
106
135
  echo "${unread_count} unread message(s):"
107
136
  echo "$unread" | jq -r '.[] | " [\(.from)] \(.subject // "(no subject)"): \(.body | if length > 120 then .[:120] + "..." else . end)"'
108
137
  fi
138
+
139
+ # --- Start poll loop ---
140
+ exec "$(dirname "$0")/poll-inbox.sh" "$RELAY_URL" "$agent_id" 10
@@ -1,33 +1,19 @@
1
1
  #!/usr/bin/env bash
2
2
  # Agent Relay plugin — SessionEnd hook
3
- # Marks this session's agent as offline.
3
+ # Marks this session's agent as offline using state file written by relay-monitor.
4
4
 
5
5
  RELAY_URL="${AGENT_RELAY_URL:-http://localhost:4850}"
6
+ instance_state="/tmp/agent-relay-instance-${PPID}.state"
6
7
 
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
- if [ -n "$CLAUDE_RIG_NAME" ]; then
14
- rig="$CLAUDE_RIG_NAME"
15
- elif [ -n "$CLAUDE_CONFIG_DIR" ]; then
16
- rig=$(basename "$CLAUDE_CONFIG_DIR")
17
- else
18
- rig="default"
19
- fi
20
-
21
- if command -v shasum >/dev/null 2>&1; then
22
- short_sid=$(printf '%s' "$session_id" | shasum -a 1 | head -c 6)
23
- else
24
- short_sid=$(printf '%s' "$session_id" | md5sum | head -c 6)
8
+ if [ -f "$instance_state" ]; then
9
+ agent_id=$(head -1 "$instance_state" 2>/dev/null)
10
+ if [ -n "$agent_id" ]; then
11
+ curl -s -X PATCH "${RELAY_URL}/api/agents/${agent_id}/status" \
12
+ -H 'Content-Type: application/json' \
13
+ -d '{"status":"offline"}' \
14
+ > /dev/null 2>&1 &
15
+ fi
16
+ rm -f "$instance_state"
25
17
  fi
26
- agent_id="${machine}-${rig}-${project}-${short_sid}"
27
-
28
- curl -s -X PATCH "${RELAY_URL}/api/agents/${agent_id}/status" \
29
- -H 'Content-Type: application/json' \
30
- -d '{"status":"offline"}' \
31
- > /dev/null 2>&1 &
32
18
 
33
19
  exit 0
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "name": "agent-relay-inbox",
4
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/relay-monitor.sh\"",
5
+ "description": "Agent Relay inbox"
6
+ }
7
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-plugin",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Claude Code plugin for Agent Relay — auto-registers sessions as agents and enables inter-agent messaging",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -8,11 +8,12 @@
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/edimuj/agent-relay.git",
11
- "directory": "plugin"
11
+ "directory": "claude"
12
12
  },
13
13
  "files": [
14
14
  ".claude-plugin/",
15
- "hooks/"
15
+ "hooks/",
16
+ "monitors/"
16
17
  ],
17
18
  "keywords": [
18
19
  "claude-code",