overmind-mcp 2.8.22 → 2.8.24
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/package.json +5 -1
- package/scripts/bridge-smoke-test.sh +76 -69
- package/scripts/post-bridge-embed.py +179 -0
- package/scripts/test-wrapper-rpc.mjs +234 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overmind-mcp",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.24",
|
|
4
4
|
"description": "Orchestrateur universel agents IA multi-modeles via MCP. Inclut le protocole 'Custom-Nickname' pour identifier vos agents avec des surnoms originaux (The Chaos Prophet, Shadow Sniper, etc.), l'isolation mémoire (Private Memory Context) et le support pour QwenCli et Nous Hermes. Installation automatique des dépendances Docker (PostgreSQL, pgvector) inclus.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"import": "./dist/index.js",
|
|
19
19
|
"types": "./dist/index.d.ts"
|
|
20
20
|
},
|
|
21
|
+
"./bridge": {
|
|
22
|
+
"import": "./dist/bridge/index.js",
|
|
23
|
+
"types": "./dist/bridge/index.d.ts"
|
|
24
|
+
},
|
|
21
25
|
"./package.json": "./package.json"
|
|
22
26
|
},
|
|
23
27
|
"files": [
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# Overmind Bridge
|
|
4
|
-
#
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# Overmind Bridge - Smoke Test (post-install validation)
|
|
4
|
+
# ============================================================================
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# Verifie en moins de 10s qu'un bridge nouvellement installe repond
|
|
7
|
+
# correctement. A lancer apres "systemctl start overmind-bridge".
|
|
8
8
|
#
|
|
9
9
|
# Usage:
|
|
10
10
|
# ./scripts/bridge-smoke-test.sh
|
|
@@ -13,21 +13,21 @@
|
|
|
13
13
|
#
|
|
14
14
|
# Exit codes:
|
|
15
15
|
# 0 = tous les tests OK
|
|
16
|
-
# 1 = au moins un test a
|
|
16
|
+
# 1 = au moins un test a echoue
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# Dependances : bash 4+, curl, jq (optionnel)
|
|
19
|
+
# ============================================================================
|
|
20
20
|
|
|
21
21
|
set -uo pipefail
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# === Defaults ===
|
|
24
24
|
HOST="127.0.0.1"
|
|
25
25
|
PORT="3100"
|
|
26
|
-
|
|
26
|
+
AUTH_TOKEN_VAL=""
|
|
27
27
|
ENV_FILE="/etc/overmind/bridge.env"
|
|
28
28
|
TIMEOUT=5
|
|
29
29
|
|
|
30
|
-
#
|
|
30
|
+
# === Couleurs, auto-desactivees si pas TTY ===
|
|
31
31
|
if [[ -t 1 ]]; then
|
|
32
32
|
C_RED=$'\033[0;31m'
|
|
33
33
|
C_GREEN=$'\033[0;32m'
|
|
@@ -36,16 +36,22 @@ if [[ -t 1 ]]; then
|
|
|
36
36
|
C_BOLD=$'\033[1m'
|
|
37
37
|
C_RESET=$'\033[0m'
|
|
38
38
|
else
|
|
39
|
-
C_RED=""
|
|
39
|
+
C_RED=""
|
|
40
|
+
C_GREEN=""
|
|
41
|
+
C_YELLOW=""
|
|
42
|
+
C_BLUE=""
|
|
43
|
+
C_BOLD=""
|
|
44
|
+
C_RESET=""
|
|
40
45
|
fi
|
|
41
46
|
|
|
42
|
-
#
|
|
47
|
+
# === Args parsing ===
|
|
43
48
|
while [[ $# -gt 0 ]]; do
|
|
44
49
|
case "$1" in
|
|
45
|
-
--host)
|
|
46
|
-
--port)
|
|
47
|
-
--auth-token)
|
|
48
|
-
--env-file)
|
|
50
|
+
--host) HOST="$2"; shift 2 ;;
|
|
51
|
+
--port) PORT="$2"; shift 2 ;;
|
|
52
|
+
--auth-token) AUTH_TOKEN_VAL="$2"; shift 2 ;;
|
|
53
|
+
--env-file) ENV_FILE="$2"; shift 2 ;;
|
|
54
|
+
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
49
55
|
-h|--help)
|
|
50
56
|
sed -n '2,20p' "$0" | sed 's/^# *//'
|
|
51
57
|
exit 0
|
|
@@ -57,55 +63,56 @@ while [[ $# -gt 0 ]]; do
|
|
|
57
63
|
esac
|
|
58
64
|
done
|
|
59
65
|
|
|
60
|
-
#
|
|
61
|
-
if [[ -z "$
|
|
62
|
-
|
|
66
|
+
# === Load token from env file si pas fourni ===
|
|
67
|
+
if [[ -z "${AUTH_TOKEN_VAL:-}" && -f "$ENV_FILE" ]]; then
|
|
68
|
+
AUTH_TOKEN_VAL="$(grep '^BRIDGE_AUTH_TOKEN=' "$ENV_FILE" | cut -d= -f2- | tr -d '"' | tr -d "'" || true)"
|
|
63
69
|
fi
|
|
64
70
|
|
|
65
71
|
BASE_URL="http://${HOST}:${PORT}"
|
|
66
72
|
PASS=0
|
|
67
73
|
FAIL=0
|
|
68
74
|
|
|
69
|
-
#
|
|
75
|
+
# === Helpers ===
|
|
70
76
|
have_jq() { command -v jq >/dev/null 2>&1; }
|
|
71
77
|
|
|
72
78
|
check() {
|
|
73
79
|
local name="$1"
|
|
74
80
|
local result="$2"
|
|
75
81
|
if [[ "$result" == "ok" ]]; then
|
|
76
|
-
echo -e " ${C_GREEN}
|
|
82
|
+
echo -e " ${C_GREEN}OK${C_RESET} $name"
|
|
77
83
|
PASS=$((PASS + 1))
|
|
78
84
|
else
|
|
79
|
-
echo -e " ${C_RED}
|
|
85
|
+
echo -e " ${C_RED}FAIL${C_RESET} $name"
|
|
80
86
|
FAIL=$((FAIL + 1))
|
|
81
87
|
fi
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
section() {
|
|
85
91
|
echo
|
|
86
|
-
echo -e "${C_BOLD}${C_BLUE}
|
|
92
|
+
echo -e "${C_BOLD}${C_BLUE}>> $1${C_RESET}"
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
#
|
|
90
|
-
echo -e "${C_BOLD}
|
|
91
|
-
echo -e "${C_BOLD} Overmind Bridge
|
|
95
|
+
# === Banner ===
|
|
96
|
+
echo -e "${C_BOLD}=======================================================${C_RESET}"
|
|
97
|
+
echo -e "${C_BOLD} Overmind Bridge - Smoke Test${C_RESET}"
|
|
92
98
|
echo -e "${C_BOLD} Target: ${BASE_URL}${C_RESET}"
|
|
93
|
-
echo -e "${C_BOLD}
|
|
99
|
+
echo -e "${C_BOLD}=======================================================${C_RESET}"
|
|
94
100
|
|
|
95
|
-
#
|
|
101
|
+
# === Test 1 : service joignable ===
|
|
96
102
|
section "1. Reachability"
|
|
97
|
-
|
|
98
|
-
"$BASE_URL/health" 2>/dev/null
|
|
99
|
-
|
|
103
|
+
HTTP_CODE=$(curl -sS --max-time "$TIMEOUT" -o /dev/null -w "%{http_code}" \
|
|
104
|
+
"$BASE_URL/health" 2>/dev/null || echo "000")
|
|
105
|
+
if echo "$HTTP_CODE" | grep -qE '^(200|401|403)$'; then
|
|
106
|
+
check "HTTP /health repond [200/401/403]" ok
|
|
100
107
|
else
|
|
101
|
-
check "HTTP /health ne
|
|
108
|
+
check "HTTP /health ne repond pas - bridge down" fail
|
|
102
109
|
echo
|
|
103
|
-
echo -e "${C_RED}
|
|
104
|
-
echo -e "
|
|
110
|
+
echo -e "${C_RED}Bridge inaccessible. Abandon.${C_RESET}"
|
|
111
|
+
echo -e " Verifier : ${C_YELLOW}systemctl status overmind-bridge${C_RESET}"
|
|
105
112
|
exit 1
|
|
106
113
|
fi
|
|
107
114
|
|
|
108
|
-
#
|
|
115
|
+
# === Test 2 : /health retourne un JSON valide ===
|
|
109
116
|
section "2. /health endpoint"
|
|
110
117
|
HEALTH_BODY=$(curl -sS --max-time "$TIMEOUT" "$BASE_URL/health" 2>/dev/null || true)
|
|
111
118
|
|
|
@@ -115,30 +122,30 @@ else
|
|
|
115
122
|
HEALTH_STATUS=$(echo "$HEALTH_BODY" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 | cut -d'"' -f4)
|
|
116
123
|
fi
|
|
117
124
|
|
|
118
|
-
if [[ -n "$HEALTH_STATUS" && "$HEALTH_STATUS" != "missing" ]]; then
|
|
119
|
-
check "/health expose un status
|
|
125
|
+
if [[ -n "${HEALTH_STATUS:-}" && "$HEALTH_STATUS" != "missing" ]]; then
|
|
126
|
+
check "/health expose un status : $HEALTH_STATUS" ok
|
|
120
127
|
else
|
|
121
|
-
check "/health n
|
|
128
|
+
check "/health n expose pas de status - endpoint casse" fail
|
|
122
129
|
fi
|
|
123
130
|
|
|
124
|
-
#
|
|
131
|
+
# === Build AUTH_HEADER array for reuse ===
|
|
125
132
|
AUTH_HEADER=()
|
|
126
|
-
if [[ -n "$
|
|
127
|
-
AUTH_HEADER=(-H "Authorization: Bearer $
|
|
133
|
+
if [[ -n "${AUTH_TOKEN_VAL:-}" ]]; then
|
|
134
|
+
AUTH_HEADER=(-H "Authorization: Bearer ${AUTH_TOKEN_VAL}")
|
|
128
135
|
fi
|
|
129
136
|
|
|
130
|
-
#
|
|
137
|
+
# === Test 3 : auth requise si token configure ===
|
|
131
138
|
section "3. Authentication"
|
|
132
|
-
if [[ -n "$
|
|
139
|
+
if [[ -n "${AUTH_TOKEN_VAL:-}" ]]; then
|
|
133
140
|
HTTP_NO_AUTH=$(curl -sS --max-time "$TIMEOUT" -o /dev/null -w "%{http_code}" \
|
|
134
141
|
-X POST "$BASE_URL/rpc" \
|
|
135
142
|
-H 'Content-Type: application/json' \
|
|
136
143
|
-d '{"jsonrpc":"2.0","id":1,"method":"health.ping","params":{}}' 2>/dev/null || echo "000")
|
|
137
144
|
|
|
138
145
|
if [[ "$HTTP_NO_AUTH" == "401" ]]; then
|
|
139
|
-
check "Sans token
|
|
146
|
+
check "Sans token -> 401, auth actif" ok
|
|
140
147
|
else
|
|
141
|
-
check "Sans token
|
|
148
|
+
check "Sans token -> $HTTP_NO_AUTH [attendu: 401]" fail
|
|
142
149
|
fi
|
|
143
150
|
|
|
144
151
|
HTTP_WITH_AUTH=$(curl -sS --max-time "$TIMEOUT" -o /dev/null -w "%{http_code}" \
|
|
@@ -148,9 +155,9 @@ if [[ -n "$TOKEN" ]]; then
|
|
|
148
155
|
-d '{"jsonrpc":"2.0","id":1,"method":"health.ping","params":{}}' 2>/dev/null || echo "000")
|
|
149
156
|
|
|
150
157
|
if [[ "$HTTP_WITH_AUTH" == "200" ]]; then
|
|
151
|
-
check "Avec token
|
|
158
|
+
check "Avec token -> 200, auth OK" ok
|
|
152
159
|
else
|
|
153
|
-
check "Avec token
|
|
160
|
+
check "Avec token -> $HTTP_WITH_AUTH [attendu: 200]" fail
|
|
154
161
|
fi
|
|
155
162
|
else
|
|
156
163
|
HTTP_OPEN=$(curl -sS --max-time "$TIMEOUT" -o /dev/null -w "%{http_code}" \
|
|
@@ -159,13 +166,13 @@ else
|
|
|
159
166
|
-d '{"jsonrpc":"2.0","id":1,"method":"health.ping","params":{}}' 2>/dev/null || echo "000")
|
|
160
167
|
|
|
161
168
|
if [[ "$HTTP_OPEN" == "200" ]]; then
|
|
162
|
-
check "/rpc ouvert
|
|
169
|
+
check "/rpc ouvert, auth non configuree" ok
|
|
163
170
|
else
|
|
164
|
-
check "/rpc ouvert
|
|
171
|
+
check "/rpc ouvert -> $HTTP_OPEN [attendu: 200]" fail
|
|
165
172
|
fi
|
|
166
173
|
fi
|
|
167
174
|
|
|
168
|
-
#
|
|
175
|
+
# === Test 4 : health.ping RPC ===
|
|
169
176
|
section "4. JSON-RPC methods"
|
|
170
177
|
PING_BODY=$(curl -sS --max-time "$TIMEOUT" \
|
|
171
178
|
"${AUTH_HEADER[@]}" \
|
|
@@ -174,12 +181,12 @@ PING_BODY=$(curl -sS --max-time "$TIMEOUT" \
|
|
|
174
181
|
-d '{"jsonrpc":"2.0","id":1,"method":"health.ping","params":{}}' 2>/dev/null || true)
|
|
175
182
|
|
|
176
183
|
if echo "$PING_BODY" | grep -q '"pong"[[:space:]]*:[[:space:]]*true'; then
|
|
177
|
-
check "RPC health.ping
|
|
184
|
+
check "RPC health.ping repond" ok
|
|
178
185
|
else
|
|
179
|
-
check "RPC health.ping
|
|
186
|
+
check "RPC health.ping echoue - body: ${PING_BODY:0:100}" fail
|
|
180
187
|
fi
|
|
181
188
|
|
|
182
|
-
#
|
|
189
|
+
# === Test 5 : JSON-RPC validation (params invalides) ===
|
|
183
190
|
section "5. Error handling"
|
|
184
191
|
INVALID_BODY=$(curl -sS --max-time "$TIMEOUT" \
|
|
185
192
|
"${AUTH_HEADER[@]}" \
|
|
@@ -188,44 +195,44 @@ INVALID_BODY=$(curl -sS --max-time "$TIMEOUT" \
|
|
|
188
195
|
-d '{"jsonrpc":"2.0","id":1,"method":"agent.run","params":{}}' 2>/dev/null || true)
|
|
189
196
|
|
|
190
197
|
if echo "$INVALID_BODY" | grep -q '"code"[[:space:]]*:[[:space:]]*-32602'; then
|
|
191
|
-
check "Params invalides
|
|
198
|
+
check "Params invalides -> -32602 Invalid params" ok
|
|
192
199
|
else
|
|
193
|
-
check "Params invalides
|
|
200
|
+
check "Params invalides - code attendu: -32602, recu: $INVALID_BODY" fail
|
|
194
201
|
fi
|
|
195
202
|
|
|
196
|
-
#
|
|
203
|
+
# === Test 6 : batch request ===
|
|
197
204
|
section "6. Batch RPC"
|
|
198
205
|
BATCH_BODY=$(curl -sS --max-time "$TIMEOUT" \
|
|
199
206
|
"${AUTH_HEADER[@]}" \
|
|
200
207
|
-H 'Content-Type: application/json' \
|
|
201
208
|
-X POST "$BASE_URL/rpc" \
|
|
202
|
-
-d '[
|
|
203
|
-
{"jsonrpc":"2.0","id":1,"method":"health.ping","params":{}}
|
|
204
|
-
{"jsonrpc":"2.0","id":2,"method":"health.ping","params":{}}
|
|
209
|
+
-d '[\
|
|
210
|
+
{"jsonrpc":"2.0","id":1,"method":"health.ping","params":{}},\
|
|
211
|
+
{"jsonrpc":"2.0","id":2,"method":"health.ping","params":{}}\
|
|
205
212
|
]' 2>/dev/null || true)
|
|
206
213
|
|
|
207
214
|
BATCH_COUNT=$(echo "$BATCH_BODY" | grep -oE '"id"[[:space:]]*:[[:space:]]*[12]' | wc -l | tr -d ' ')
|
|
208
215
|
if [[ "$BATCH_COUNT" == "2" ]]; then
|
|
209
|
-
check "Batch RPC retourne 2
|
|
216
|
+
check "Batch RPC retourne 2 reponses" ok
|
|
210
217
|
else
|
|
211
|
-
check "Batch RPC
|
|
218
|
+
check "Batch RPC - recu: $BATCH_COUNT reponses [attendu: 2]" fail
|
|
212
219
|
fi
|
|
213
220
|
|
|
214
|
-
#
|
|
221
|
+
# === Resume ===
|
|
215
222
|
echo
|
|
216
|
-
echo -e "${C_BOLD}
|
|
223
|
+
echo -e "${C_BOLD}=======================================================${C_RESET}"
|
|
217
224
|
if [[ $FAIL -eq 0 ]]; then
|
|
218
|
-
echo -e "${C_GREEN}${C_BOLD}
|
|
219
|
-
echo -e "${C_GREEN} Bridge
|
|
220
|
-
echo -e "${C_BOLD}
|
|
225
|
+
echo -e "${C_GREEN}${C_BOLD} OK $PASS tests OK, 0 echec${C_RESET}"
|
|
226
|
+
echo -e "${C_GREEN} Bridge operationnel sur ${BASE_URL}${C_RESET}"
|
|
227
|
+
echo -e "${C_BOLD}=======================================================${C_RESET}"
|
|
221
228
|
exit 0
|
|
222
229
|
else
|
|
223
|
-
echo -e "${C_RED}${C_BOLD}
|
|
230
|
+
echo -e "${C_RED}${C_BOLD} FAIL $FAIL echec(s), $PASS tests OK${C_RESET}"
|
|
224
231
|
echo
|
|
225
232
|
echo -e " ${C_YELLOW}Diagnostic :${C_RESET}"
|
|
226
233
|
echo -e " 1. ${C_YELLOW}systemctl status overmind-bridge${C_RESET}"
|
|
227
234
|
echo -e " 2. ${C_YELLOW}journalctl -u overmind-bridge -n 100${C_RESET}"
|
|
228
235
|
echo -e " 3. ${C_YELLOW}curl -v $BASE_URL/health${C_RESET}"
|
|
229
|
-
echo -e "${C_BOLD}
|
|
236
|
+
echo -e "${C_BOLD}=======================================================${C_RESET}"
|
|
230
237
|
exit 1
|
|
231
238
|
fi
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Post l'embed du wrapper Overmind Bridge sur Discord."""
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import urllib.request
|
|
6
|
+
import urllib.error
|
|
7
|
+
|
|
8
|
+
# Récupère le token depuis .env
|
|
9
|
+
env_path = r"C:\Users\Deamon\Desktop\Backup\Serveur MCP\discord_llm\.env"
|
|
10
|
+
TOKEN_PREFIX = "DISCORD_BOT_TOKEN"
|
|
11
|
+
token = None
|
|
12
|
+
with open(env_path, 'r', encoding='utf-8') as f:
|
|
13
|
+
for line in f:
|
|
14
|
+
if line.startswith(TOKEN_PREFIX + "="):
|
|
15
|
+
token = line.split('=', 1)[1].strip()
|
|
16
|
+
break
|
|
17
|
+
if not token:
|
|
18
|
+
raise SystemExit("Token non trouve dans .env")
|
|
19
|
+
|
|
20
|
+
CHANNEL_ID = "1491078210279182500"
|
|
21
|
+
|
|
22
|
+
embed = {
|
|
23
|
+
"title": "🌉 Overmind Bridge — Couche RPC unifiée",
|
|
24
|
+
"description": (
|
|
25
|
+
"Couche **JSON-RPC 2.0** stable au-dessus du serveur MCP Overmind.\n"
|
|
26
|
+
"Transforme n'importe quel client HTTP (curl, Discord, SMS, autre agent) "
|
|
27
|
+
"en orchestrateur multi-agents avec **15 methodes RPC**, session store, "
|
|
28
|
+
"circuit breaker, retry, et webhook adapter prets a l'emploi.\n\n"
|
|
29
|
+
"Paquetage prod : `import { ... } from 'overmind-mcp/bridge'`"
|
|
30
|
+
),
|
|
31
|
+
"color": 0x5865F2,
|
|
32
|
+
"fields": [
|
|
33
|
+
{
|
|
34
|
+
"name": "⚡ Demarrage rapide",
|
|
35
|
+
"value": (
|
|
36
|
+
"```\n"
|
|
37
|
+
"POST /rpc\n"
|
|
38
|
+
"Content-Type: application/json\n\n"
|
|
39
|
+
"{\n"
|
|
40
|
+
' "jsonrpc": "2.0",\n'
|
|
41
|
+
' "id": 1,\n'
|
|
42
|
+
' "method": "agent.run",\n'
|
|
43
|
+
' "params": {\n'
|
|
44
|
+
' "agentName": "sniperbot_analyst",\n'
|
|
45
|
+
' "runner": "hermes",\n'
|
|
46
|
+
' "prompt": "...",\n'
|
|
47
|
+
' "externalKey": "+1418..."\n'
|
|
48
|
+
" }\n"
|
|
49
|
+
"}\n"
|
|
50
|
+
"```"
|
|
51
|
+
),
|
|
52
|
+
"inline": False
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "📚 15 methodes RPC (5 categories)",
|
|
56
|
+
"value": (
|
|
57
|
+
"▸ **Health** : `health.ping`\n"
|
|
58
|
+
"▸ **Agent** : `agent.run` · `agent.a2a` · `agent.status` · `agent.list` · `agent.kill`\n"
|
|
59
|
+
"▸ **MessageLog** : `message.history` · `message.get` · `message.replay` · `message.stats`\n"
|
|
60
|
+
"▸ **Session** : `session.get` · `session.list` · `session.delete` · `session.stats`\n"
|
|
61
|
+
"▸ **Webhook** : `webhook.sms` (voipms · twilio · discord · generic)"
|
|
62
|
+
),
|
|
63
|
+
"inline": False
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "🛡️ Features production",
|
|
67
|
+
"value": (
|
|
68
|
+
"✅ Circuit breaker (5 echecs → open 30s)\n"
|
|
69
|
+
"✅ Triple timeout (connect + body + per-chunk)\n"
|
|
70
|
+
"✅ Retry auto sur ETIMEDOUT/EBODYREAD/ECONNRESET\n"
|
|
71
|
+
"✅ SessionStore multi-tenant (phone, userId, channelId)\n"
|
|
72
|
+
"✅ DirectiveParser (auto-update SESSION_ID: reponse)\n"
|
|
73
|
+
"✅ AgentRegistry + mutex (1 run par agent)\n"
|
|
74
|
+
"✅ MessageLog Postgres (optionnel)\n"
|
|
75
|
+
"✅ Path traversal bloque\n"
|
|
76
|
+
"✅ JSON-RPC 2.0 strict (batch + error codes)"
|
|
77
|
+
),
|
|
78
|
+
"inline": False
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "🏗️ Architecture (3 couches)",
|
|
82
|
+
"value": (
|
|
83
|
+
"```\n"
|
|
84
|
+
"Client → BridgeProxy\n"
|
|
85
|
+
" ↓\n"
|
|
86
|
+
" OverBridgeService (session + types)\n"
|
|
87
|
+
" ↓\n"
|
|
88
|
+
" OverBridgeServer (HTTP + /rpc + /webhook)\n"
|
|
89
|
+
" ↓\n"
|
|
90
|
+
" Overmind MCP :3099 (run_agent)\n"
|
|
91
|
+
"```"
|
|
92
|
+
),
|
|
93
|
+
"inline": False
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "📊 Codes d'erreur (JSON-RPC 2.0)",
|
|
97
|
+
"value": (
|
|
98
|
+
"▸ `-32700` PARSE_ERROR (JSON malforme)\n"
|
|
99
|
+
"▸ `-32600` INVALID_REQUEST\n"
|
|
100
|
+
"▸ `-32601` METHOD_NOT_FOUND\n"
|
|
101
|
+
"▸ `-32602` INVALID_PARAMS\n"
|
|
102
|
+
"▸ `-32603` INTERNAL_ERROR\n"
|
|
103
|
+
"▸ `-32003` FEATURE_DISABLED (ex: MessageLog off)\n"
|
|
104
|
+
"▸ `-32004` NOT_FOUND (ex: UUID inconnu)"
|
|
105
|
+
),
|
|
106
|
+
"inline": False
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "🔌 Endpoints HTTP",
|
|
110
|
+
"value": (
|
|
111
|
+
"▸ `POST /rpc` — JSON-RPC 2.0 principal\n"
|
|
112
|
+
"▸ `GET /health` — Liveness probe (K8s/Docker)\n"
|
|
113
|
+
"▸ `POST /webhook/:provider` — voipms/twilio/discord/generic\n"
|
|
114
|
+
"▸ `GET /f/:filename` — Static files (path traversal bloque)"
|
|
115
|
+
),
|
|
116
|
+
"inline": False
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "🚀 Clients deja migres",
|
|
120
|
+
"value": (
|
|
121
|
+
"✅ **discord_llm v5.0** — 100% aligne sur le wrapper, expose `/rpc` + 11 methodes testees sur sniperbot_analyst\n"
|
|
122
|
+
"🔄 **bt-sms** — migration prevue (copy-paste du pattern discord_llm)"
|
|
123
|
+
),
|
|
124
|
+
"inline": False
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "🧪 Test live (sniperbot_analyst)",
|
|
128
|
+
"value": (
|
|
129
|
+
"```bash\n"
|
|
130
|
+
"# 1. Run agent avec session multi-tenant\n"
|
|
131
|
+
"curl -X POST http://127.0.0.1:3001/rpc -d '...agent.run...'\n"
|
|
132
|
+
"# 2. A2A loopback\n"
|
|
133
|
+
"curl -X POST http://127.0.0.1:3001/rpc -d '...agent.a2a...'\n"
|
|
134
|
+
"# 3. Webhook VoIP.ms → auto-dispatch\n"
|
|
135
|
+
"curl -X POST http://127.0.0.1:3001/rpc -d '...webhook.sms...'\n"
|
|
136
|
+
"```\n"
|
|
137
|
+
"Tous testes ✅ sur discord_llm v5.0"
|
|
138
|
+
),
|
|
139
|
+
"inline": False
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"footer": {
|
|
143
|
+
"text": "Overmind Bridge v5.0 · discord_llm v5.0 · sniperbot_analyst sur runner=hermes (glm-5.1)"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
payload = {"embeds": [embed]}
|
|
148
|
+
data = json.dumps(payload).encode('utf-8')
|
|
149
|
+
|
|
150
|
+
req = urllib.request.Request(
|
|
151
|
+
f"https://discord.com/api/v10/channels/{CHANNEL_ID}/messages",
|
|
152
|
+
data=data,
|
|
153
|
+
method='POST',
|
|
154
|
+
headers={
|
|
155
|
+
'Authorization': f'Bot {token}',
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
'User-Agent': 'DiscordBot (overmind-bridge, 5.0.0)',
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
163
|
+
body = resp.read().decode('utf-8')
|
|
164
|
+
result = json.loads(body)
|
|
165
|
+
print(f"OK: embed poste")
|
|
166
|
+
print(f" message_id = {result.get('id')}")
|
|
167
|
+
print(f" channel_id = {result.get('channel_id')}")
|
|
168
|
+
guild = result.get('guild_id')
|
|
169
|
+
if guild:
|
|
170
|
+
print(f" url = https://discord.com/channels/{guild}/{result.get('channel_id')}/{result.get('id')}")
|
|
171
|
+
else:
|
|
172
|
+
print(f" url = https://discord.com/channels/@me/{result.get('channel_id')}/{result.get('id')}")
|
|
173
|
+
except urllib.error.HTTPError as e:
|
|
174
|
+
print(f"ERREUR HTTP {e.code}: {e.reason}")
|
|
175
|
+
try:
|
|
176
|
+
print(f" body: {e.read().decode('utf-8')[:1000]}")
|
|
177
|
+
except: pass
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"ERREUR: {e}")
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test standalone des nouvelles fonctionnalités RPC du wrapper.
|
|
4
|
+
*
|
|
5
|
+
* Lance un OverBridgeServer sur :3100 qui pointe vers le MCP live (:3099)
|
|
6
|
+
* et exerce chaque méthode RPC documentée.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node scripts/test-wrapper-rpc.mjs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { OverBridgeService, OverBridgeServer } from '../dist/bridge/index.js';
|
|
12
|
+
|
|
13
|
+
const WRAPPER_PORT = 3100;
|
|
14
|
+
const UPSTREAM_MCP = process.env.MCP_URL || 'http://localhost:3099/mcp';
|
|
15
|
+
const BASE = `http://127.0.0.1:${WRAPPER_PORT}`;
|
|
16
|
+
let pass = 0;
|
|
17
|
+
let fail = 0;
|
|
18
|
+
const results = [];
|
|
19
|
+
|
|
20
|
+
const service = new OverBridgeService(
|
|
21
|
+
{ mcpUrl: UPSTREAM_MCP, defaultTimeoutMs: 60_000, agentTimeoutMs: 180_000 },
|
|
22
|
+
{
|
|
23
|
+
debug: (m) => console.log(` [debug] ${m}`),
|
|
24
|
+
info: (m) => console.log(` [info] ${m}`),
|
|
25
|
+
warn: (m) => console.log(` [warn] ${m}`),
|
|
26
|
+
error: (m) => console.log(` [error] ${m}`),
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const server = new OverBridgeServer(
|
|
31
|
+
service,
|
|
32
|
+
{
|
|
33
|
+
port: WRAPPER_PORT,
|
|
34
|
+
host: '127.0.0.1',
|
|
35
|
+
postgres: {
|
|
36
|
+
host: 'localhost', port: 5432, user: 'postgres', password: '', database: 'overmind_memory',
|
|
37
|
+
},
|
|
38
|
+
enableMessageLog: false, // on désactive MessageLog (pas de Postgres ici)
|
|
39
|
+
enableSessionStore: true, // multi-tenant (in-memory)
|
|
40
|
+
enableDirectives: true,
|
|
41
|
+
enableWebhooks: true,
|
|
42
|
+
healthCheckIntervalMs: 0, // pas de heartbeat
|
|
43
|
+
jsonBodyLimit: '5mb',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
debug: () => {},
|
|
47
|
+
info: (m) => console.log(` [server] ${m}`),
|
|
48
|
+
warn: (m) => console.log(` [server.warn] ${m}`),
|
|
49
|
+
error: (m) => console.log(` [server.error] ${m}`),
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
async function rpc(method, params = {}, id = 1) {
|
|
54
|
+
const res = await fetch(`${BASE}/rpc`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
57
|
+
body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
|
|
58
|
+
});
|
|
59
|
+
return await res.json();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function check(name, ok, detail = '') {
|
|
63
|
+
if (ok) { pass++; results.push(` PASS ${name}${detail ? ' - ' + detail : ''}`); }
|
|
64
|
+
else { fail++; results.push(` FAIL ${name}${detail ? ' - ' + detail : ''}`); }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
69
|
+
console.log(' Test Standalone - Wrapper RPC');
|
|
70
|
+
console.log(` Wrapper: ${BASE}/rpc`);
|
|
71
|
+
console.log(` Upstream MCP: ${UPSTREAM_MCP}`);
|
|
72
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
73
|
+
|
|
74
|
+
await server.start();
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
// ─── 1. health.ping ──────────────────────────────────────────
|
|
78
|
+
{
|
|
79
|
+
const r = await rpc('health.ping');
|
|
80
|
+
check('health.ping', r.result?.pong === true, `pong=${r.result?.pong}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── 2. agent.list (filtre runner=hermes) ───────────────────
|
|
84
|
+
{
|
|
85
|
+
const r = await rpc('agent.list', { runner: 'hermes' });
|
|
86
|
+
const agents = JSON.parse(r.result?.agents?.[0] || '[]');
|
|
87
|
+
// Le retour est wrappé dans content[0].text (format MCP).
|
|
88
|
+
// On accepte si pas d'erreur.
|
|
89
|
+
check('agent.list (runner=hermes)', !r.error, `agents.length=${r.result?.stats?.total}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── 3. agent.status (state local) ──────────────────────────
|
|
93
|
+
{
|
|
94
|
+
const r = await rpc('agent.status', { agentName: 'sniperbot_analyst' });
|
|
95
|
+
const hasLocal = r.result?.local !== undefined;
|
|
96
|
+
check('agent.status', hasLocal, hasLocal ? `status=${r.result.local.status}` : JSON.stringify(r).slice(0,200));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── 4. agent.run AVEC externalKey (SessionStore) ──────────
|
|
100
|
+
{
|
|
101
|
+
const r = await rpc('agent.run', {
|
|
102
|
+
agentName: 'sniperbot_analyst',
|
|
103
|
+
runner: 'hermes',
|
|
104
|
+
prompt: 'Test SessionStore — reponds juste OK',
|
|
105
|
+
externalKey: 'sniper-test-phone-001',
|
|
106
|
+
});
|
|
107
|
+
const hasSession = r.result?.sessionId !== undefined;
|
|
108
|
+
check('agent.run + externalKey', hasSession && !r.error?.message?.includes('Invalid'),
|
|
109
|
+
`sessionId=${r.result?.sessionId}, content[0]=${r.result?.content?.[0]?.text?.slice(0,80)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── 5. session.get ───────────────────────────────────────
|
|
113
|
+
{
|
|
114
|
+
const r = await rpc('session.get', {
|
|
115
|
+
externalKey: 'sniper-test-phone-001',
|
|
116
|
+
agentName: 'sniperbot_analyst',
|
|
117
|
+
});
|
|
118
|
+
const found = r.result?.session !== null && r.result?.session !== undefined;
|
|
119
|
+
check('session.get', found, `sessionId=${r.result?.session?.sessionId?.slice(0,20)}...`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── 6. session.list ──────────────────────────────────────
|
|
123
|
+
{
|
|
124
|
+
const r = await rpc('session.list', {});
|
|
125
|
+
const list = r.result?.sessions || [];
|
|
126
|
+
check('session.list', Array.isArray(list) && list.length > 0, `count=${list.length}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── 7. session.stats ─────────────────────────────────────
|
|
130
|
+
{
|
|
131
|
+
const r = await rpc('session.stats', {});
|
|
132
|
+
const total = r.result?.total;
|
|
133
|
+
check('session.stats', typeof total === 'number', `total=${total}, byAgent=${JSON.stringify(r.result?.byAgent)}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── 8. webhook.sms avec autoDispatch (voipms → sniperbot) ─
|
|
137
|
+
{
|
|
138
|
+
const r = await rpc('webhook.sms', {
|
|
139
|
+
provider: 'voipms',
|
|
140
|
+
payload: {
|
|
141
|
+
from: '+14181234567',
|
|
142
|
+
to: '+14187654321',
|
|
143
|
+
message: 'BTC test via webhook',
|
|
144
|
+
id: 'msg-test-001',
|
|
145
|
+
date: new Date().toISOString(),
|
|
146
|
+
},
|
|
147
|
+
externalKey: 'sniper-test-phone-001',
|
|
148
|
+
autoDispatch: {
|
|
149
|
+
agentName: 'sniperbot_analyst',
|
|
150
|
+
runner: 'hermes',
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
const dispatched = r.result?.messageId !== undefined || r.result?.content !== undefined;
|
|
154
|
+
check('webhook.sms + autoDispatch', dispatched || !r.error,
|
|
155
|
+
dispatched ? `messageId=${r.result?.messageId?.slice(0,8)}...` : JSON.stringify(r).slice(0,200));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── 9. batch RPC (array de 3 requêtes) ───────────────────
|
|
159
|
+
{
|
|
160
|
+
const batch = await fetch(`${BASE}/rpc`, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
body: JSON.stringify([
|
|
164
|
+
{ jsonrpc: '2.0', id: 1, method: 'health.ping', params: {} },
|
|
165
|
+
{ jsonrpc: '2.0', id: 2, method: 'health.ping', params: {} },
|
|
166
|
+
{ jsonrpc: '2.0', id: 3, method: 'health.ping', params: {} },
|
|
167
|
+
]),
|
|
168
|
+
});
|
|
169
|
+
const arr = await batch.json();
|
|
170
|
+
const allOk = Array.isArray(arr) && arr.length === 3 && arr.every((r) => r.result?.pong === true);
|
|
171
|
+
check('batch RPC (3 requêtes)', allOk, `count=${arr.length}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ─── 10. method unknown → -32601 ──────────────────────────
|
|
175
|
+
{
|
|
176
|
+
const r = await rpc('does.not.exist', {});
|
|
177
|
+
check('method unknown → -32601', r.error?.code === -32601, `code=${r.error?.code}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── 11. params invalides → -32602 ────────────────────────
|
|
181
|
+
{
|
|
182
|
+
const r = await rpc('agent.run', {}); // manque agentName, runner, prompt
|
|
183
|
+
check('params invalides → -32602', r.error?.code === -32602, `code=${r.error?.code}, issues=${r.error?.data?.length || 0}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── 12. /health endpoint HTTP ────────────────────────────
|
|
187
|
+
{
|
|
188
|
+
const r = await fetch(`${BASE}/health`).then((r) => r.json());
|
|
189
|
+
const hasFields = r.status && r.mcp && r.features !== undefined;
|
|
190
|
+
check('GET /health', hasFields, `status=${r.status}, features.sessionStore=${r.features?.sessionStore}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ─── 13. /webhook/:provider endpoint HTTP ───────────────────
|
|
194
|
+
{
|
|
195
|
+
const r = await fetch(`${BASE}/webhook/voipms`, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers: { 'Content-Type': 'application/json' },
|
|
198
|
+
body: JSON.stringify({ from: '+14181112222', message: 'hi', id: 'm1' }),
|
|
199
|
+
});
|
|
200
|
+
const j = await r.json();
|
|
201
|
+
check('POST /webhook/voipms', j.received === true, `provider=${j.provider}, externalKey=${j.externalKey}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─── 14. path traversal bloqué sur /f/ ────────────────────
|
|
205
|
+
{
|
|
206
|
+
const r = await fetch(`${BASE}/f/..%2Fetc%2Fpasswd`);
|
|
207
|
+
const status = r.status;
|
|
208
|
+
check('path traversal bloqué', status === 403 || status === 400, `status=${status}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── 15. session.delete ───────────────────────────────────
|
|
212
|
+
{
|
|
213
|
+
const r = await rpc('session.delete', {
|
|
214
|
+
externalKey: 'sniper-test-phone-001',
|
|
215
|
+
agentName: 'sniperbot_analyst',
|
|
216
|
+
});
|
|
217
|
+
check('session.delete', r.result?.deleted === true, `deleted=${r.result?.deleted}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ─── Cleanup ─────────────────────────────────────────────
|
|
221
|
+
await server.stop();
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
224
|
+
for (const line of results) console.log(line);
|
|
225
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
226
|
+
console.log(` ${pass} pass, ${fail} fail`);
|
|
227
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
228
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
main().catch((err) => {
|
|
232
|
+
console.error('FATAL:', err);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
});
|