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.
- package/.claude-plugin/plugin.json +1 -1
- package/hooks/approval-gate.sh +10 -2
- package/hooks/poll-inbox.sh +18 -9
- package/hooks/profile-lib.sh +72 -0
- package/hooks/relay-monitor.sh +39 -13
- package/package.json +1 -1
package/hooks/approval-gate.sh
CHANGED
|
@@ -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
|
-
|
|
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)
|
package/hooks/poll-inbox.sh
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
--
|
|
63
|
-
--argjson
|
|
64
|
-
--argjson
|
|
65
|
-
|
|
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
|
+
}
|
package/hooks/relay-monitor.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
--
|
|
66
|
-
--argjson
|
|
67
|
-
--argjson
|
|
68
|
-
|
|
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
|
|
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 "$
|
|
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
|
-
|
|
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