overmind-mcp 2.8.23 → 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 +1 -1
- 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",
|
|
@@ -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
|
+
});
|