slashvibe-mcp 0.3.23 → 0.3.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/bridges/agent-gateway.js +351 -0
- package/bridges/bridge-monitor.js +399 -0
- package/bridges/discord-bot.js +421 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +416 -0
- package/bridges/webhook-server.js +461 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +406 -0
- package/games/arcade.js +403 -0
- package/games/chess.js +460 -0
- package/games/colorguess.js +344 -0
- package/games/crossword-words.js +175 -0
- package/games/crossword.js +463 -0
- package/games/drawing.js +352 -0
- package/games/gameroulette.js +290 -0
- package/games/gamerouter.js +334 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +330 -0
- package/games/memory.js +360 -0
- package/games/multiplayer-tictactoe.js +406 -0
- package/games/pixelart.js +406 -0
- package/games/quickduel.js +382 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +284 -0
- package/games/snake.js +408 -0
- package/games/storybuilder.js +351 -0
- package/games/tictactoe.js +350 -0
- package/games/twentyquestions.js +379 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +506 -0
- package/games/wordassociation.js +293 -0
- package/games/wordchain.js +158 -0
- package/package.json +3 -1
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Gateway Bridge — Event push + local state for external agents
|
|
3
|
+
*
|
|
4
|
+
* The /vibe platform API (slashvibe.dev) already provides:
|
|
5
|
+
* - Messaging: POST/GET /api/messages
|
|
6
|
+
* - Presence: POST/GET /api/presence
|
|
7
|
+
* - Board: POST/GET /api/board (ships, ideas, requests)
|
|
8
|
+
* - Discovery: GET /api/discover
|
|
9
|
+
* - Agents: GET /api/agents
|
|
10
|
+
* - Auth: JWT via /api/auth/*
|
|
11
|
+
*
|
|
12
|
+
* This bridge fills the GAPS for external agent gateways (Clawdbot, @seth):
|
|
13
|
+
*
|
|
14
|
+
* 1. EVENT PUSH — Platform is pull-based. This pushes events to agents.
|
|
15
|
+
* 2. LOCAL STATE — Memory, reservations, and session data are local-only.
|
|
16
|
+
* 3. AIRC IDENTITY — Verifies agent identity via Ed25519 signatures.
|
|
17
|
+
* 4. AGENT REGISTRY — Tracks which agents are connected + their capabilities.
|
|
18
|
+
*
|
|
19
|
+
* External agents should use the platform API directly for:
|
|
20
|
+
* DMs, presence, board, discovery, profiles
|
|
21
|
+
* And use THIS bridge for:
|
|
22
|
+
* Event subscriptions, local memory queries, AIRC verification
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const crypto = require('../crypto');
|
|
26
|
+
const config = require('../config');
|
|
27
|
+
const memory = require('../memory');
|
|
28
|
+
const debug = require('../debug');
|
|
29
|
+
|
|
30
|
+
// ============ AGENT REGISTRY ============
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Known agent gateways
|
|
34
|
+
* @type {Map<string, {handle: string, publicKey: string, endpoint: string, capabilities: string[], registeredAt: number}>}
|
|
35
|
+
*/
|
|
36
|
+
const agentRegistry = new Map();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Event subscriptions — agents subscribe to event types and get HTTP pushes
|
|
40
|
+
* @type {Map<string, {endpoint: string, events: string[], handle: string}>}
|
|
41
|
+
*/
|
|
42
|
+
const eventSubscriptions = new Map();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Register an external agent gateway with AIRC identity
|
|
46
|
+
*
|
|
47
|
+
* @param {object} params
|
|
48
|
+
* @param {string} params.handle Agent handle (e.g. "seth-agent")
|
|
49
|
+
* @param {string} params.publicKey Base64 Ed25519 public key (AIRC)
|
|
50
|
+
* @param {string} [params.endpoint] HTTP callback URL for event pushes
|
|
51
|
+
* @param {string[]} [params.capabilities] What this agent can do
|
|
52
|
+
* @param {string} [params.signature] AIRC signature proving key ownership
|
|
53
|
+
* @returns {{success: boolean, agentId?: string, error?: string}}
|
|
54
|
+
*/
|
|
55
|
+
function registerAgent({ handle, publicKey, endpoint, capabilities = [], signature }) {
|
|
56
|
+
if (!handle || !publicKey) {
|
|
57
|
+
return { success: false, error: 'handle and publicKey required' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Verify AIRC signature if provided (proves private key ownership)
|
|
61
|
+
if (signature) {
|
|
62
|
+
const valid = crypto.verify(
|
|
63
|
+
{ handle, publicKey, endpoint, capabilities },
|
|
64
|
+
publicKey
|
|
65
|
+
);
|
|
66
|
+
if (!valid) {
|
|
67
|
+
return { success: false, error: 'Invalid AIRC signature' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const agentId = `agent_${handle}_${Date.now().toString(36)}`;
|
|
72
|
+
|
|
73
|
+
agentRegistry.set(handle, {
|
|
74
|
+
agentId,
|
|
75
|
+
handle,
|
|
76
|
+
publicKey,
|
|
77
|
+
endpoint: endpoint || null,
|
|
78
|
+
capabilities,
|
|
79
|
+
registeredAt: Date.now()
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
debug(`[agent-gateway] Registered @${handle} (${agentId})`);
|
|
83
|
+
return { success: true, agentId, handle };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Verify an AIRC-signed message from a registered agent
|
|
88
|
+
*
|
|
89
|
+
* @param {object} message Signed message with `from` and `signature` fields
|
|
90
|
+
* @returns {{valid: boolean, handle?: string, verified?: string, error?: string}}
|
|
91
|
+
*/
|
|
92
|
+
function verifyAgentMessage(message) {
|
|
93
|
+
if (!message || !message.from) {
|
|
94
|
+
return { valid: false, error: 'Missing from field' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const agent = agentRegistry.get(message.from);
|
|
98
|
+
|
|
99
|
+
// AIRC-verified: registered agent with valid signature
|
|
100
|
+
if (agent && message.signature) {
|
|
101
|
+
const valid = crypto.verify(message, agent.publicKey);
|
|
102
|
+
if (!valid) {
|
|
103
|
+
return { valid: false, error: 'AIRC signature verification failed' };
|
|
104
|
+
}
|
|
105
|
+
return { valid: true, handle: message.from, verified: 'airc' };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Registered but unsigned — allow with lower trust
|
|
109
|
+
if (agent && !message.signature) {
|
|
110
|
+
debug(`[agent-gateway] Unsigned request from registered agent @${message.from}`);
|
|
111
|
+
return { valid: true, handle: message.from, verified: 'registered' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { valid: false, error: `Unknown agent: ${message.from}. Register first via POST /agent/register` };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============ EVENT PUSH ============
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Subscribe an agent to /vibe events (push model)
|
|
121
|
+
*
|
|
122
|
+
* Event types:
|
|
123
|
+
* - dm: New direct messages for the subscribed handle
|
|
124
|
+
* - mention: @mentions in feed/board
|
|
125
|
+
* - ship: New ships from connections
|
|
126
|
+
* - presence: People coming online/offline
|
|
127
|
+
* - handoff: Task handoff requests
|
|
128
|
+
*
|
|
129
|
+
* @param {string} handle Agent handle
|
|
130
|
+
* @param {string} endpoint HTTP callback URL to receive events
|
|
131
|
+
* @param {string[]} events Event types to subscribe to
|
|
132
|
+
* @returns {{success: boolean, subscribed: string[]}}
|
|
133
|
+
*/
|
|
134
|
+
function subscribe(handle, endpoint, events = ['dm', 'mention', 'ship', 'presence']) {
|
|
135
|
+
if (!endpoint) {
|
|
136
|
+
return { success: false, error: 'endpoint required' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
eventSubscriptions.set(handle, { endpoint, events, handle });
|
|
140
|
+
debug(`[agent-gateway] @${handle} subscribed to [${events.join(', ')}] → ${endpoint}`);
|
|
141
|
+
return { success: true, subscribed: events };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Unsubscribe an agent from events
|
|
146
|
+
* @param {string} handle Agent handle
|
|
147
|
+
*/
|
|
148
|
+
function unsubscribe(handle) {
|
|
149
|
+
eventSubscriptions.delete(handle);
|
|
150
|
+
debug(`[agent-gateway] @${handle} unsubscribed`);
|
|
151
|
+
return { success: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Push an event to all subscribed agents
|
|
156
|
+
* AIRC-signed if we have a keypair (proves event came from /vibe)
|
|
157
|
+
*
|
|
158
|
+
* Called by notify.js and tool handlers when events occur.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} eventType Event type (dm, mention, ship, presence, handoff)
|
|
161
|
+
* @param {object} eventData Event payload
|
|
162
|
+
*/
|
|
163
|
+
async function pushEvent(eventType, eventData) {
|
|
164
|
+
const keypair = config.getKeypair();
|
|
165
|
+
const myHandle = config.getHandle();
|
|
166
|
+
|
|
167
|
+
for (const [handle, sub] of eventSubscriptions) {
|
|
168
|
+
if (!sub.events.includes(eventType)) continue;
|
|
169
|
+
if (!sub.endpoint) continue;
|
|
170
|
+
|
|
171
|
+
const event = {
|
|
172
|
+
v: '0.1',
|
|
173
|
+
type: 'vibe_event',
|
|
174
|
+
event: eventType,
|
|
175
|
+
data: eventData,
|
|
176
|
+
from: myHandle || 'vibe-mcp',
|
|
177
|
+
timestamp: Math.floor(Date.now() / 1000)
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// AIRC sign so receiver can verify this came from /vibe
|
|
181
|
+
if (keypair) {
|
|
182
|
+
event.signature = crypto.sign(event, keypair.privateKey);
|
|
183
|
+
event.publicKey = keypair.publicKey;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const response = await fetch(sub.endpoint, {
|
|
188
|
+
method: 'POST',
|
|
189
|
+
headers: {
|
|
190
|
+
'Content-Type': 'application/json',
|
|
191
|
+
'X-Vibe-Event': eventType,
|
|
192
|
+
'X-Vibe-Source': 'vibe-mcp'
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify(event)
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
debug(`[agent-gateway] Push to @${handle} failed: HTTP ${response.status}`);
|
|
199
|
+
}
|
|
200
|
+
} catch (e) {
|
|
201
|
+
debug(`[agent-gateway] Push to @${handle} failed: ${e.message}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============ LOCAL STATE QUERIES ============
|
|
207
|
+
// These expose data that lives only in the MCP process, not on the platform API
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Query local memory (thread-scoped JSONL files)
|
|
211
|
+
* Platform API doesn't have memory — it's local-first by design
|
|
212
|
+
*/
|
|
213
|
+
function queryMemory(handle, limit = 10, search = null) {
|
|
214
|
+
const memories = memory.recall(handle, limit);
|
|
215
|
+
|
|
216
|
+
if (search && memories.length > 0) {
|
|
217
|
+
return memories.filter(m =>
|
|
218
|
+
m.observation.toLowerCase().includes(search.toLowerCase())
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return memories;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Store a memory observation (local-first)
|
|
227
|
+
*/
|
|
228
|
+
function storeMemory(handle, observation) {
|
|
229
|
+
memory.remember(handle, observation);
|
|
230
|
+
return { success: true, handle, observation };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* List all memory threads
|
|
235
|
+
*/
|
|
236
|
+
function listMemoryThreads() {
|
|
237
|
+
return memory.listThreads();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ============ HTTP HANDLER ============
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* HTTP handler for the agent gateway
|
|
244
|
+
*
|
|
245
|
+
* Routes:
|
|
246
|
+
* POST /agent/register — Register agent with AIRC public key
|
|
247
|
+
* POST /agent/subscribe — Subscribe to event pushes
|
|
248
|
+
* POST /agent/unsubscribe — Unsubscribe from events
|
|
249
|
+
* POST /agent/memory — Query/store local memory
|
|
250
|
+
* GET /agent/status — Gateway health + registered agents
|
|
251
|
+
*
|
|
252
|
+
* For everything else, agents hit the platform API directly:
|
|
253
|
+
* POST https://slashvibe.dev/api/messages — Send DMs
|
|
254
|
+
* GET https://slashvibe.dev/api/presence — Who's online
|
|
255
|
+
* POST https://slashvibe.dev/api/board — Ship/idea/request
|
|
256
|
+
* GET https://slashvibe.dev/api/discover — Find people
|
|
257
|
+
* GET https://slashvibe.dev/api/agents — Agent directory
|
|
258
|
+
*/
|
|
259
|
+
async function handleRequest(req) {
|
|
260
|
+
const { path, method, body } = req;
|
|
261
|
+
|
|
262
|
+
// Health / status
|
|
263
|
+
if (path === '/agent/status' && method === 'GET') {
|
|
264
|
+
const agents = [];
|
|
265
|
+
for (const [, agent] of agentRegistry) {
|
|
266
|
+
agents.push({
|
|
267
|
+
handle: agent.handle,
|
|
268
|
+
capabilities: agent.capabilities,
|
|
269
|
+
registeredAt: agent.registeredAt,
|
|
270
|
+
hasEndpoint: !!agent.endpoint
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
status: 'ok',
|
|
276
|
+
agents,
|
|
277
|
+
subscriptions: eventSubscriptions.size,
|
|
278
|
+
version: '0.1.0',
|
|
279
|
+
platform_api: config.getApiUrl(),
|
|
280
|
+
note: 'For DMs, presence, board, discovery — use the platform API directly'
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (method !== 'POST') {
|
|
285
|
+
return { error: 'Method not allowed', status: 405 };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
289
|
+
|
|
290
|
+
switch (path) {
|
|
291
|
+
case '/agent/register':
|
|
292
|
+
return registerAgent(data);
|
|
293
|
+
|
|
294
|
+
case '/agent/subscribe': {
|
|
295
|
+
const v = verifyAgentMessage(data);
|
|
296
|
+
if (!v.valid) return { success: false, error: v.error, status: 401 };
|
|
297
|
+
return subscribe(v.handle, data.endpoint, data.events);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
case '/agent/unsubscribe': {
|
|
301
|
+
const v = verifyAgentMessage(data);
|
|
302
|
+
if (!v.valid) return { success: false, error: v.error, status: 401 };
|
|
303
|
+
return unsubscribe(v.handle);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
case '/agent/memory': {
|
|
307
|
+
const v = verifyAgentMessage(data);
|
|
308
|
+
if (!v.valid) return { success: false, error: v.error, status: 401 };
|
|
309
|
+
|
|
310
|
+
if (data.action === 'recall') {
|
|
311
|
+
const memories = queryMemory(data.handle, data.limit, data.search);
|
|
312
|
+
return { success: true, memories };
|
|
313
|
+
}
|
|
314
|
+
if (data.action === 'remember') {
|
|
315
|
+
return storeMemory(data.handle, data.observation);
|
|
316
|
+
}
|
|
317
|
+
if (data.action === 'threads') {
|
|
318
|
+
return { success: true, threads: listMemoryThreads() };
|
|
319
|
+
}
|
|
320
|
+
return { success: false, error: 'action must be: recall, remember, or threads' };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
default:
|
|
324
|
+
return { error: 'Not found', status: 404 };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ============ EXPORTS ============
|
|
329
|
+
|
|
330
|
+
module.exports = {
|
|
331
|
+
// Registration
|
|
332
|
+
registerAgent,
|
|
333
|
+
verifyAgentMessage,
|
|
334
|
+
|
|
335
|
+
// Event push (the main value-add over platform API)
|
|
336
|
+
subscribe,
|
|
337
|
+
unsubscribe,
|
|
338
|
+
pushEvent,
|
|
339
|
+
|
|
340
|
+
// Local state (not on platform)
|
|
341
|
+
queryMemory,
|
|
342
|
+
storeMemory,
|
|
343
|
+
listMemoryThreads,
|
|
344
|
+
|
|
345
|
+
// HTTP handler
|
|
346
|
+
handleRequest,
|
|
347
|
+
|
|
348
|
+
// Registry access
|
|
349
|
+
getAgentRegistry: () => agentRegistry,
|
|
350
|
+
getSubscriptions: () => eventSubscriptions
|
|
351
|
+
};
|