agent-relay-plugin 0.4.16 → 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 +28 -11
- 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
|
@@ -6,6 +6,7 @@
|
|
|
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"
|
|
9
10
|
plugin_root="${CLAUDE_PLUGIN_ROOT:-$(cd "${script_dir}/.." && pwd)}"
|
|
10
11
|
PLUGIN_VERSION=$(
|
|
11
12
|
jq -r '.version // empty' "${plugin_root}/.claude-plugin/plugin.json" 2>/dev/null ||
|
|
@@ -30,6 +31,7 @@ elif [ -n "$CLAUDE_CONFIG_DIR" ]; then
|
|
|
30
31
|
else
|
|
31
32
|
rig="default"
|
|
32
33
|
fi
|
|
34
|
+
ar_init_profile "claude" "$rig" "$project"
|
|
33
35
|
|
|
34
36
|
if command -v shasum >/dev/null 2>&1; then
|
|
35
37
|
short_pid=$(printf '%s' "$PPID" | shasum -a 1 | head -c 6)
|
|
@@ -57,11 +59,6 @@ if [ -f "$instance_state" ]; then
|
|
|
57
59
|
fi
|
|
58
60
|
printf '%s\n' "$agent_id" > "$instance_state"
|
|
59
61
|
|
|
60
|
-
# --- Register agent ---
|
|
61
|
-
caps_csv="${AGENT_RELAY_CAPS:-chat}"
|
|
62
|
-
caps_json=$(echo "$caps_csv" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; ""))')
|
|
63
|
-
approval="${AGENT_RELAY_APPROVAL:-open}"
|
|
64
|
-
|
|
65
62
|
reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agents" "${auth_header_args[@]}" \
|
|
66
63
|
-H 'Content-Type: application/json' \
|
|
67
64
|
-d "$(jq -n \
|
|
@@ -69,10 +66,11 @@ reg_code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "${RELAY_URL}/api/agen
|
|
|
69
66
|
--arg name "$project ($rig @ $machine)" \
|
|
70
67
|
--arg machine "$machine" \
|
|
71
68
|
--arg rig "$rig" \
|
|
72
|
-
--
|
|
73
|
-
--argjson
|
|
74
|
-
--argjson
|
|
75
|
-
|
|
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)'
|
|
76
74
|
)" 2>/dev/null)
|
|
77
75
|
|
|
78
76
|
if [ "$reg_code" != "201" ] && [ "$reg_code" != "200" ]; then
|
|
@@ -92,7 +90,7 @@ fi
|
|
|
92
90
|
|
|
93
91
|
# --- Output context (first stdout = notification injected into conversation) ---
|
|
94
92
|
approval_note=""
|
|
95
|
-
case "$
|
|
93
|
+
case "$AR_APPROVAL" in
|
|
96
94
|
guarded) approval_note=" — destructive ops (rm, mv, chmod, kill, force-push) are blocked" ;;
|
|
97
95
|
read-only) approval_note=" — observe/analyze/report only, no file writes or mutation commands" ;;
|
|
98
96
|
esac
|
|
@@ -100,7 +98,10 @@ esac
|
|
|
100
98
|
cat <<CONTEXT
|
|
101
99
|
Agent Relay active. Your agent ID: ${agent_id}
|
|
102
100
|
Relay URL: ${RELAY_URL} | Server: ${server_version:-unknown} | Plugin: ${PLUGIN_VERSION}
|
|
103
|
-
|
|
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}
|
|
104
105
|
|
|
105
106
|
To send a message:
|
|
106
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"}'
|
|
@@ -141,6 +142,22 @@ To find agents by capability:
|
|
|
141
142
|
To list online agents:
|
|
142
143
|
curl -s${auth_header_example} '${RELAY_URL}/api/agents?status=online'
|
|
143
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
|
+
|
|
144
161
|
System messages:
|
|
145
162
|
Messages prefixed with "⚠ SYSTEM" are relay-level instructions (e.g. plugin updates, restart requests).
|
|
146
163
|
When you see one, follow the instruction immediately — for example, restart the inbox monitor if asked.
|
package/package.json
CHANGED