agent-relay-plugin 0.4.15 → 0.4.17

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.4.15"
4
+ "version": "0.4.17"
5
5
  }
@@ -1,8 +1,16 @@
1
1
  #!/usr/bin/env bash
2
2
  # Agent Relay plugin — approval gate for PreToolUse and PermissionDenied hooks.
3
- # Enforces AGENT_RELAY_APPROVAL policy: open (default), guarded, read-only.
3
+ # Enforces AGENT_RELAY_APPROVAL/profile approval policy: open (default), guarded, read-only.
4
4
 
5
- MODE="${AGENT_RELAY_APPROVAL:-open}"
5
+ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
6
+ source "${script_dir}/profile-lib.sh"
7
+ project=$(basename "${PWD:-unknown}")
8
+ if [ -n "$CLAUDE_RIG_NAME" ]; then rig="$CLAUDE_RIG_NAME"
9
+ elif [ -n "$CLAUDE_CONFIG_DIR" ]; then rig=$(basename "$CLAUDE_CONFIG_DIR")
10
+ else rig="default"; fi
11
+ ar_init_profile "claude" "$rig" "$project"
12
+ MODE="${AGENT_RELAY_APPROVAL:-$AR_APPROVAL}"
13
+ MODE="${MODE:-open}"
6
14
  [ "$MODE" = "open" ] && exit 0
7
15
 
8
16
  input=$(cat)
@@ -6,6 +6,8 @@
6
6
  RELAY_URL="$1"
7
7
  AGENT_ID="$2"
8
8
  INTERVAL="${3:-10}"
9
+ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
10
+ source "${script_dir}/profile-lib.sh"
9
11
  auth_header_args=()
10
12
  if [ -n "${AGENT_RELAY_TOKEN:-}" ]; then
11
13
  auth_header_args=(-H "X-Agent-Relay-Token: ${AGENT_RELAY_TOKEN}")
@@ -19,6 +21,11 @@ fi
19
21
  # PID file so session-start.sh can find and kill us on /clear or /compact.
20
22
  pid_file="/tmp/agent-relay-poll-${AGENT_ID}.pid"
21
23
  status_file="/tmp/agent-relay-status-${AGENT_ID}.state"
24
+ project=$(basename "${PWD:-unknown}")
25
+ if [ -n "$CLAUDE_RIG_NAME" ]; then rig="$CLAUDE_RIG_NAME"
26
+ elif [ -n "$CLAUDE_CONFIG_DIR" ]; then rig=$(basename "$CLAUDE_CONFIG_DIR")
27
+ else rig="default"; fi
28
+ ar_init_profile "claude" "$rig" "$project"
22
29
  cleanup() { rm -f "$pid_file"; }
23
30
  trap cleanup EXIT
24
31
  echo $$ > "$pid_file"
@@ -42,15 +49,13 @@ current_status() {
42
49
  }
43
50
 
44
51
  re_register() {
45
- local machine rig project caps_csv caps_json approval reg_code
52
+ local machine rig project reg_code
46
53
  machine=$(hostname)
47
54
  project=$(basename "${PWD:-unknown}")
48
55
  if [ -n "$CLAUDE_RIG_NAME" ]; then rig="$CLAUDE_RIG_NAME"
49
56
  elif [ -n "$CLAUDE_CONFIG_DIR" ]; then rig=$(basename "$CLAUDE_CONFIG_DIR")
50
57
  else rig="default"; fi
51
- caps_csv="${AGENT_RELAY_CAPS:-chat}"
52
- caps_json=$(echo "$caps_csv" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
53
- approval="${AGENT_RELAY_APPROVAL:-open}"
58
+ ar_init_profile "claude" "$rig" "$project"
54
59
 
55
60
  reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agents" "${auth_header_args[@]}" \
56
61
  -H 'Content-Type: application/json' \
@@ -59,10 +64,11 @@ re_register() {
59
64
  --arg name "$project ($rig @ $machine)" \
60
65
  --arg machine "$machine" \
61
66
  --arg rig "$rig" \
62
- --argjson tags "$(jq -n --arg r "$rig" --arg p "$project" '[$r, $p]')" \
63
- --argjson caps "$caps_json" \
64
- --argjson meta "$(jq -n --arg cwd "$PWD" --arg approval "$approval" '{cwd: $cwd, approvalMode: $approval}')" \
65
- '{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta}'
67
+ --arg label "$AR_LABEL" \
68
+ --argjson tags "$AR_TAGS_JSON" \
69
+ --argjson caps "$AR_CAPS_JSON" \
70
+ --argjson meta "$AR_META_JSON" \
71
+ '{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta} + (if $label != "" then {label: $label} else {} end)'
66
72
  )" 2>/dev/null)
67
73
 
68
74
  [ "$reg_code" = "200" ] || [ "$reg_code" = "201" ]
@@ -133,6 +139,9 @@ while true; do
133
139
  echo "$msgs" | jq -c '.[]' | while IFS= read -r msg; do
134
140
  mid=$(printf '%s' "$msg" | jq -r '.id')
135
141
  claimable=$(printf '%s' "$msg" | jq -r '.claimable // false')
142
+ if [ "$(ar_message_matches_channels "$msg")" != "true" ]; then
143
+ continue
144
+ fi
136
145
 
137
146
  if [ "$claimable" = "true" ]; then
138
147
  claim_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \
@@ -144,7 +153,7 @@ while true; do
144
153
  fi
145
154
  fi
146
155
 
147
- printf '%s' "$msg" | jq -r 'if .type == "system" then "⚠ SYSTEM [msg:\(.id)]: \(.body)" else "[msg:\(.id)] \(.from) → \(.to) | \(.subject // "(no subject)"): \(.body)" end'
156
+ printf '%s' "$msg" | jq -r '(.meta.pairId // empty) as $pair | (if $pair != "" then " [pair:\($pair)]" else "" end) as $pairText | if .type == "system" then "⚠ SYSTEM [msg:\(.id)]\($pairText): \(.body)" else "[msg:\(.id)]\($pairText) \(.from) → \(.to) | \(.subject // "(no subject)"): \(.body)" end'
148
157
 
149
158
  curl -s -X PATCH "${RELAY_URL}/api/messages/${mid}" "${auth_header_args[@]}" \
150
159
  -H 'Content-Type: application/json' \
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ar_csv_json() {
4
+ printf '%s' "$1" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0)) | reduce .[] as $x ([]; if index($x) then . else . + [$x] end)'
5
+ }
6
+
7
+ ar_profile_value() {
8
+ local key="$1"
9
+ printf '%s' "${AR_PROFILE_JSON:-{}}" | jq -r --arg key "$key" '.[$key] // empty | if type == "string" then gsub("^\\s+|\\s+$"; "") else empty end'
10
+ }
11
+
12
+ ar_profile_array() {
13
+ local key="$1"
14
+ printf '%s' "${AR_PROFILE_JSON:-{}}" | jq -c --arg key "$key" 'if (.[$key] | type) == "array" then .[$key] | map(select(type == "string") | gsub("^\\s+|\\s+$"; "") | select(length > 0)) | reduce .[] as $x ([]; if index($x) then . else . + [$x] end) else [] end'
15
+ }
16
+
17
+ ar_init_profile() {
18
+ local provider="$1"
19
+ local rig="$2"
20
+ local project="$3"
21
+ local profiles_file profile_meta extra_tags
22
+
23
+ AR_PROFILE_NAME="${AGENT_RELAY_PROFILE:-}"
24
+ profiles_file="${AGENT_RELAY_PROFILES_FILE:-${HOME:-}/.config/agent-relay/profiles.json}"
25
+ AR_PROFILE_JSON="{}"
26
+ if [ -n "$AR_PROFILE_NAME" ] && [ -f "$profiles_file" ]; then
27
+ AR_PROFILE_JSON=$(jq -c --arg name "$AR_PROFILE_NAME" '.[$name] // {}' "$profiles_file" 2>/dev/null || printf '{}')
28
+ fi
29
+
30
+ if [ -n "${AGENT_RELAY_CAPS:-}" ]; then
31
+ AR_CAPS_JSON=$(ar_csv_json "$AGENT_RELAY_CAPS")
32
+ else
33
+ AR_CAPS_JSON=$(ar_profile_array capabilities)
34
+ if [ "$AR_CAPS_JSON" = "[]" ]; then AR_CAPS_JSON='["chat"]'; fi
35
+ fi
36
+
37
+ if [ -n "${AGENT_RELAY_TAGS:-}" ]; then
38
+ extra_tags=$(ar_csv_json "$AGENT_RELAY_TAGS")
39
+ else
40
+ extra_tags=$(ar_profile_array tags)
41
+ fi
42
+ AR_TAGS_JSON=$(jq -n --arg provider "$provider" --arg rig "$rig" --arg project "$project" --argjson extra "$extra_tags" \
43
+ '[$provider, $rig, $project] + $extra | map(select(length > 0)) | reduce .[] as $x ([]; if index($x) then . else . + [$x] end)')
44
+
45
+ if [ -n "${AGENT_RELAY_CHANNELS:-}" ]; then
46
+ AR_CHANNELS_JSON=$(ar_csv_json "$AGENT_RELAY_CHANNELS")
47
+ else
48
+ AR_CHANNELS_JSON=$(ar_profile_array channels)
49
+ fi
50
+
51
+ AR_LABEL="${AGENT_RELAY_LABEL:-$(ar_profile_value label)}"
52
+ AR_APPROVAL="${AGENT_RELAY_APPROVAL:-$(ar_profile_value approval)}"
53
+ AR_APPROVAL="${AR_APPROVAL:-open}"
54
+ case "$AR_APPROVAL" in
55
+ open|guarded|read-only) ;;
56
+ *) AR_APPROVAL="open" ;;
57
+ esac
58
+ profile_meta=$(printf '%s' "$AR_PROFILE_JSON" | jq -c 'if (.meta | type) == "object" then .meta else {} end' 2>/dev/null || printf '{}')
59
+ AR_META_JSON=$(jq -n \
60
+ --arg cwd "${PWD:-}" \
61
+ --arg approval "$AR_APPROVAL" \
62
+ --arg profile "$AR_PROFILE_NAME" \
63
+ --argjson channels "$AR_CHANNELS_JSON" \
64
+ --argjson profileMeta "$profile_meta" \
65
+ '$profileMeta + {cwd: $cwd, approvalMode: $approval, channels: $channels} + (if $profile != "" then {profile: $profile} else {} end)')
66
+ }
67
+
68
+ ar_message_matches_channels() {
69
+ local msg="$1"
70
+ printf '%s' "$msg" | jq -r --argjson channels "${AR_CHANNELS_JSON:-[]}" \
71
+ '(.channel // "") as $ch | if (($channels | length) == 0) or ($ch == "") or (($channels | index($ch)) != null) then "true" else "false" end'
72
+ }
@@ -5,7 +5,15 @@
5
5
  # PPID = Claude Code process, stable across /clear and /compact.
6
6
 
7
7
  RELAY_URL="${AGENT_RELAY_URL:-http://localhost:4850}"
8
- PLUGIN_VERSION=$(jq -r '.version' "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/plugin.json" 2>/dev/null || echo "unknown")
8
+ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
9
+ source "${script_dir}/profile-lib.sh"
10
+ plugin_root="${CLAUDE_PLUGIN_ROOT:-$(cd "${script_dir}/.." && pwd)}"
11
+ PLUGIN_VERSION=$(
12
+ jq -r '.version // empty' "${plugin_root}/.claude-plugin/plugin.json" 2>/dev/null ||
13
+ jq -r '.version // empty' "${plugin_root}/package.json" 2>/dev/null ||
14
+ true
15
+ )
16
+ PLUGIN_VERSION="${PLUGIN_VERSION:-unknown}"
9
17
  auth_header_args=()
10
18
  auth_header_example=' ${AGENT_RELAY_TOKEN:+-H "X-Agent-Relay-Token: ${AGENT_RELAY_TOKEN}"}'
11
19
  if [ -n "${AGENT_RELAY_TOKEN:-}" ]; then
@@ -23,6 +31,7 @@ elif [ -n "$CLAUDE_CONFIG_DIR" ]; then
23
31
  else
24
32
  rig="default"
25
33
  fi
34
+ ar_init_profile "claude" "$rig" "$project"
26
35
 
27
36
  if command -v shasum >/dev/null 2>&1; then
28
37
  short_pid=$(printf '%s' "$PPID" | shasum -a 1 | head -c 6)
@@ -50,11 +59,6 @@ if [ -f "$instance_state" ]; then
50
59
  fi
51
60
  printf '%s\n' "$agent_id" > "$instance_state"
52
61
 
53
- # --- Register agent ---
54
- caps_csv="${AGENT_RELAY_CAPS:-chat}"
55
- caps_json=$(echo "$caps_csv" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
56
- approval="${AGENT_RELAY_APPROVAL:-open}"
57
-
58
62
  reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agents" "${auth_header_args[@]}" \
59
63
  -H 'Content-Type: application/json' \
60
64
  -d "$(jq -n \
@@ -62,10 +66,11 @@ reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agen
62
66
  --arg name "$project ($rig @ $machine)" \
63
67
  --arg machine "$machine" \
64
68
  --arg rig "$rig" \
65
- --argjson tags "$(jq -n --arg r "$rig" --arg p "$project" '[$r, $p]')" \
66
- --argjson caps "$caps_json" \
67
- --argjson meta "$(jq -n --arg cwd "$PWD" --arg approval "$approval" '{cwd: $cwd, approvalMode: $approval}')" \
68
- '{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta}'
69
+ --arg label "$AR_LABEL" \
70
+ --argjson tags "$AR_TAGS_JSON" \
71
+ --argjson caps "$AR_CAPS_JSON" \
72
+ --argjson meta "$AR_META_JSON" \
73
+ '{id: $id, name: $name, machine: $machine, rig: $rig, tags: $tags, capabilities: $caps, status: "online", meta: $meta} + (if $label != "" then {label: $label} else {} end)'
69
74
  )" 2>/dev/null)
70
75
 
71
76
  if [ "$reg_code" != "201" ] && [ "$reg_code" != "200" ]; then
@@ -77,13 +82,15 @@ server_version=$(curl -s "${auth_header_args[@]}" "${RELAY_URL}/api/stats" 2>/de
77
82
  version_warning=""
78
83
  if [ -z "$server_version" ]; then
79
84
  version_warning="⚠ Server does not report its version — consider updating: bunx agent-relay-server@latest"
85
+ elif [ "$PLUGIN_VERSION" = "unknown" ]; then
86
+ version_warning="⚠ Version mismatch — server ${server_version}, plugin unknown. Restart Claude Code or update the Agent Relay plugin."
80
87
  elif [ "$server_version" != "$PLUGIN_VERSION" ]; then
81
- version_warning="⚠ Version mismatch — server ${server_version}, plugin ${PLUGIN_VERSION}. Update server: bunx agent-relay-server@latest"
88
+ version_warning="⚠ Version mismatch — server ${server_version}, plugin ${PLUGIN_VERSION}. Update/restart Agent Relay server and plugin so the versions match."
82
89
  fi
83
90
 
84
91
  # --- Output context (first stdout = notification injected into conversation) ---
85
92
  approval_note=""
86
- case "$approval" in
93
+ case "$AR_APPROVAL" in
87
94
  guarded) approval_note=" — destructive ops (rm, mv, chmod, kill, force-push) are blocked" ;;
88
95
  read-only) approval_note=" — observe/analyze/report only, no file writes or mutation commands" ;;
89
96
  esac
@@ -91,7 +98,10 @@ esac
91
98
  cat <<CONTEXT
92
99
  Agent Relay active. Your agent ID: ${agent_id}
93
100
  Relay URL: ${RELAY_URL} | Server: ${server_version:-unknown} | Plugin: ${PLUGIN_VERSION}
94
- Approval mode: ${approval}${approval_note}
101
+ Profile: ${AR_PROFILE_NAME:-none} | Label: ${AR_LABEL:-none}
102
+ Tags: $(printf '%s' "$AR_TAGS_JSON" | jq -r 'join(", ")') | Capabilities: $(printf '%s' "$AR_CAPS_JSON" | jq -r 'join(", ")')
103
+ Channels: $(printf '%s' "$AR_CHANNELS_JSON" | jq -r 'if length == 0 then "all" else join(", ") end')
104
+ Approval mode: ${AR_APPROVAL}${approval_note}
95
105
 
96
106
  To send a message:
97
107
  curl -s -X POST ${RELAY_URL}/api/messages${auth_header_example} -H 'Content-Type: application/json' -d '{"from":"${agent_id}","to":"TARGET","body":"MESSAGE"}'
@@ -132,6 +142,22 @@ To find agents by capability:
132
142
  To list online agents:
133
143
  curl -s${auth_header_example} '${RELAY_URL}/api/agents?status=online'
134
144
 
145
+ To pair with one available agent for focused two-party live collaboration:
146
+ If the user types "/pair codex", "/pair status", "/pair send PAIR_ID MESSAGE", "/disconnect", "/status", "/label LABEL", or "/tags TAGS", run the matching agent-relay CLI command.
147
+ agent-relay /pair codex "WORK TO SOLVE TOGETHER"
148
+ agent-relay /pair status
149
+ agent-relay /pair send PAIR_ID "MESSAGE"
150
+ agent-relay /disconnect
151
+ agent-relay /status
152
+ agent-relay /label backend-fixer
153
+ agent-relay /tags backend tests urgent
154
+ If the CLI is unavailable, use the API directly:
155
+ curl -s -X POST ${RELAY_URL}/api/pairs${auth_header_example} -H 'Content-Type: application/json' -d '{"from":"${agent_id}","target":"codex","objective":"WORK TO SOLVE TOGETHER"}'
156
+ Accept an incoming pair invite: curl -s -X POST ${RELAY_URL}/api/pairs/PAIR_ID/accept${auth_header_example} -H 'Content-Type: application/json' -d '{"agentId":"${agent_id}"}'
157
+ Send pair chat: curl -s -X POST ${RELAY_URL}/api/pairs/PAIR_ID/messages${auth_header_example} -H 'Content-Type: application/json' -d '{"from":"${agent_id}","body":"MESSAGE"}'
158
+ Hang up: curl -s -X POST ${RELAY_URL}/api/pairs/PAIR_ID/hangup${auth_header_example} -H 'Content-Type: application/json' -d '{"agentId":"${agent_id}"}'
159
+ Pairing is exclusive: one agent can have only one pending or active pair.
160
+
135
161
  System messages:
136
162
  Messages prefixed with "⚠ SYSTEM" are relay-level instructions (e.g. plugin updates, restart requests).
137
163
  When you see one, follow the instruction immediately — for example, restart the inbox monitor if asked.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-plugin",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
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": "AGPL-3.0-or-later",