agentschat-mcp 0.31.0 → 0.32.0

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/dist/connector.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ // connector/run.ts
3
+ import WS from "ws";
4
+
2
5
  // connector/server.ts
3
6
  import { createServer } from "node:http";
4
7
  import { WebSocketServer, WebSocket } from "ws";
@@ -101,14 +104,58 @@ function toWireEvent(msg, platform = "agentschat") {
101
104
  };
102
105
  }
103
106
 
107
+ // connector/identities.ts
108
+ class IdentityTable {
109
+ byBot = new Map;
110
+ constructor(identities) {
111
+ for (const id of identities) {
112
+ if (this.byBot.has(id.botId)) {
113
+ throw new Error(`duplicate identity botId "${id.botId}" — ambiguous routing`);
114
+ }
115
+ this.byBot.set(id.botId, id);
116
+ }
117
+ }
118
+ forBot(botId) {
119
+ return this.byBot.get(botId) ?? null;
120
+ }
121
+ isSingle() {
122
+ return this.byBot.size === 1;
123
+ }
124
+ get size() {
125
+ return this.byBot.size;
126
+ }
127
+ all() {
128
+ return [...this.byBot.values()];
129
+ }
130
+ }
131
+ function routeInbound(table, ctx) {
132
+ if (ctx.channel_id?.startsWith("dm-")) {
133
+ return ctx.dmOwnerBotId ? table.forBot(ctx.dmOwnerBotId) : null;
134
+ }
135
+ const mentioned = Array.isArray(ctx.mentioned_ids) ? ctx.mentioned_ids : [];
136
+ for (const mid of mentioned) {
137
+ const id = table.forBot(mid);
138
+ if (id)
139
+ return id;
140
+ }
141
+ return null;
142
+ }
143
+
104
144
  // connector/server.ts
105
145
  function startConnector(config) {
106
146
  const log = config.logger ?? (() => {});
107
147
  const descriptor = buildDescriptor(config.descriptor);
148
+ const single = !config.identities || config.identities.length === 0;
149
+ const table = new IdentityTable(single ? [{ botId: "default", agentId: "default", token: "", gatewayId: "", secret: "" }] : config.identities);
150
+ const hooks = single ? {
151
+ sendMessage: (_b, chatId, content, replyTo) => config.agentschat.sendMessage(chatId, content, replyTo),
152
+ getChatInfo: (_b, chatId) => config.agentschat.getChatInfo(chatId),
153
+ sendTyping: (_b, chatId) => config.agentschat.sendTyping?.(chatId) ?? Promise.resolve()
154
+ } : config.agentschat;
108
155
  const sockets = new Set;
109
156
  const http = createServer((_req, res) => {
110
157
  res.writeHead(200, { "Content-Type": "application/json" });
111
- res.end(JSON.stringify({ ok: true, service: "agentschat-connector", contract_version: 1 }));
158
+ res.end(JSON.stringify({ ok: true, service: "agentschat-connector", contract_version: 1, identities: table.size }));
112
159
  });
113
160
  const wss = new WebSocketServer({ noServer: true });
114
161
  http.on("upgrade", (req, socket, head) => {
@@ -134,52 +181,75 @@ function startConnector(config) {
134
181
  });
135
182
  });
136
183
  function onConnection(ws, gatewayId) {
137
- sockets.add(ws);
184
+ const conn = { ws, gatewayId, fronted: new Set };
185
+ sockets.add(conn);
138
186
  log(`[connector] gateway connected: ${gatewayId} (${sockets.size} total)`);
139
187
  ws.on("message", async (data) => {
140
- let frame;
141
- try {
142
- frame = JSON.parse(data.toString());
143
- } catch {
144
- return;
188
+ const text = data.toString();
189
+ for (const line of text.split(`
190
+ `)) {
191
+ if (!line.trim())
192
+ continue;
193
+ let frame;
194
+ try {
195
+ frame = JSON.parse(line);
196
+ } catch {
197
+ continue;
198
+ }
199
+ await handleFrame(conn, frame).catch((e) => log(`[connector] frame error: ${e}`));
145
200
  }
146
- await handleFrame(ws, frame).catch((e) => log(`[connector] frame error: ${e}`));
147
201
  });
148
202
  ws.on("close", () => {
149
- sockets.delete(ws);
203
+ sockets.delete(conn);
150
204
  log(`[connector] gateway disconnected: ${gatewayId} (${sockets.size} left)`);
151
205
  });
152
- ws.on("error", () => sockets.delete(ws));
206
+ ws.on("error", () => sockets.delete(conn));
153
207
  }
154
- async function handleFrame(ws, frame) {
208
+ async function handleFrame(conn, frame) {
155
209
  const t = frame?.type;
156
210
  if (t === "hello") {
157
- send(ws, { type: "descriptor", descriptor });
211
+ const botId = String(frame.botId ?? "");
212
+ const identity = single ? table.all()[0] : botId ? table.forBot(botId) : null;
213
+ if (!identity) {
214
+ send(conn.ws, { type: "error", error: `unknown identity botId: ${botId || "(none)"}` });
215
+ return;
216
+ }
217
+ conn.fronted.add(identity.botId);
218
+ send(conn.ws, { type: "descriptor", descriptor: { ...descriptor, platform: "agentschat" } });
158
219
  return;
159
220
  }
160
221
  if (t === "outbound") {
161
- const result = await handleOutbound(frame.action ?? {});
162
- send(ws, { type: "outbound_result", requestId: frame.requestId, result });
222
+ const result = await handleOutbound(conn, frame);
223
+ send(conn.ws, { type: "outbound_result", requestId: frame.requestId, result });
163
224
  return;
164
225
  }
165
226
  }
166
- async function handleOutbound(action) {
227
+ async function handleOutbound(conn, frame) {
228
+ const action = frame?.action ?? {};
167
229
  const op = action?.op;
168
230
  const chatId = action?.chat_id ?? "";
231
+ const firstFronted = [...conn.fronted][0];
232
+ const requested = typeof frame?.botId === "string" && frame.botId ? frame.botId : firstFronted ?? null;
233
+ const identity = single ? table.all()[0] : requested && conn.fronted.has(requested) ? table.forBot(requested) : null;
234
+ if (!identity) {
235
+ log(`[connector] outbound failed: no fronted identity for botId=${requested ?? "?"}`);
236
+ return { success: false, error: `no fronted identity for outbound (botId=${requested ?? "?"})` };
237
+ }
169
238
  switch (op) {
170
239
  case "send": {
171
- const r = await config.agentschat.sendMessage(chatId, action.content ?? "", action.reply_to);
240
+ const r = await hooks.sendMessage(identity.botId, chatId, action.content ?? "", action.reply_to);
172
241
  return { success: true, message_id: r?.id };
173
242
  }
174
243
  case "typing": {
175
- await config.agentschat.sendTyping?.(chatId);
244
+ await hooks.sendTyping?.(identity.botId, chatId);
176
245
  return { success: true };
177
246
  }
178
247
  case "get_chat_info": {
179
- const info = await config.agentschat.getChatInfo(chatId);
248
+ const info = await hooks.getChatInfo(identity.botId, chatId);
180
249
  return info ?? {};
181
250
  }
182
251
  default:
252
+ log(`[connector] outbound failed: unsupported op ${String(op)}`);
183
253
  return { success: false, error: `unsupported op: ${String(op)}` };
184
254
  }
185
255
  }
@@ -194,20 +264,33 @@ function startConnector(config) {
194
264
  return {
195
265
  port,
196
266
  stop() {
197
- for (const ws of sockets) {
267
+ for (const c of sockets) {
198
268
  try {
199
- ws.close(1001, "connector shutdown");
269
+ c.ws.close(1001, "connector shutdown");
200
270
  } catch {}
201
271
  }
202
272
  wss.close();
203
273
  http.close();
204
274
  },
205
275
  injectAgentsChatMessage(msg) {
206
- const event = toWireEvent(msg, descriptor.platform);
276
+ const bySocket = msg.__botId ? table.forBot(String(msg.__botId)) : null;
277
+ const target = bySocket ?? routeInbound(table, {
278
+ channel_id: msg.channel_id,
279
+ mentioned_ids: msg.mentioned_ids,
280
+ dmOwnerBotId: msg.dm_owner
281
+ }) ?? (table.isSingle() ? table.all()[0] : null);
282
+ if (!target) {
283
+ log(`[connector] inbound unaddressed (channel=${msg.channel_id ?? "?"} mentions=${JSON.stringify(msg.mentioned_ids ?? [])}) — dropped`);
284
+ return;
285
+ }
286
+ const event = toWireEvent(msg, "agentschat");
207
287
  if (!event)
208
288
  return;
209
- for (const ws of sockets)
210
- send(ws, { type: "inbound", event });
289
+ event.source.profile = target.botId;
290
+ for (const conn of sockets) {
291
+ if (conn.fronted.has(target.botId))
292
+ send(conn.ws, { type: "inbound", event });
293
+ }
211
294
  },
212
295
  connections() {
213
296
  return sockets.size;
@@ -227,7 +310,6 @@ function peekPayload(token) {
227
310
  }
228
311
 
229
312
  // connector/run.ts
230
- import WS from "ws";
231
313
  var log = (m) => process.stderr.write(`[agentschat-connector] ${m}
232
314
  `);
233
315
  function need(name) {
@@ -238,96 +320,139 @@ function need(name) {
238
320
  }
239
321
  return v;
240
322
  }
241
- var AGENT_ID = need("AGENTCHAT_AGENT_ID");
242
- var TOKEN = need("AGENTCHAT_TOKEN");
243
- var GATEWAY_ID = need("RELAY_GATEWAY_ID");
244
- var GATEWAY_SECRET = need("RELAY_GATEWAY_SECRET");
245
323
  var API = (process.env.AGENTCHAT_API_URL || "https://agents-chat.com").replace(/\/$/, "");
246
324
  var WS_URL = process.env.AGENTCHAT_WS_URL || API.replace(/^http/, "ws") + "/ws";
247
325
  var PORT = Number(process.env.RELAY_PORT || 8765);
248
326
  var HOST = process.env.RELAY_HOST || "127.0.0.1";
327
+ function resolveIdentities() {
328
+ const raw = (process.env.RELAY_IDENTITIES || "").trim();
329
+ if (raw) {
330
+ let parsed;
331
+ try {
332
+ parsed = JSON.parse(raw);
333
+ } catch (e) {
334
+ log(`ERROR: RELAY_IDENTITIES is not valid JSON: ${e}`);
335
+ process.exit(1);
336
+ }
337
+ if (!Array.isArray(parsed) || parsed.length === 0) {
338
+ log(`ERROR: RELAY_IDENTITIES must be a non-empty JSON array`);
339
+ process.exit(1);
340
+ }
341
+ for (const it of parsed) {
342
+ if (!it?.botId || !it?.token || !it?.gatewayId || !it?.secret) {
343
+ log(`ERROR: each RELAY_IDENTITIES entry needs botId, token, gatewayId, secret — got: ${JSON.stringify(it).slice(0, 80)}`);
344
+ process.exit(1);
345
+ }
346
+ }
347
+ return parsed.map((it) => ({
348
+ botId: String(it.botId),
349
+ agentId: String(it.agentId ?? it.botId),
350
+ token: String(it.token),
351
+ gatewayId: String(it.gatewayId),
352
+ secret: String(it.secret)
353
+ }));
354
+ }
355
+ const agentId = need("AGENTCHAT_AGENT_ID");
356
+ const token = need("AGENTCHAT_TOKEN");
357
+ const gatewayId = need("RELAY_GATEWAY_ID");
358
+ const secret = need("RELAY_GATEWAY_SECRET");
359
+ return [{ botId: agentId, agentId, token, gatewayId, secret }];
360
+ }
361
+ var identities = resolveIdentities();
362
+ var single = identities.length === 1;
363
+ var secrets = {};
364
+ for (const id of identities) {
365
+ (secrets[id.gatewayId] ??= []).push(id.secret);
366
+ }
249
367
  var broadcast = null;
368
+ var socketsByBot = new Map;
369
+ var backoffByBot = new Map;
370
+ function connectIdentity(id) {
371
+ const ws = new WS(WS_URL);
372
+ socketsByBot.set(id.botId, ws);
373
+ ws.on("open", () => {
374
+ backoffByBot.set(id.botId, 1000);
375
+ try {
376
+ ws.send(JSON.stringify({ type: "auth", agent_id: id.agentId, token: id.token, capabilities: ["chat"] }));
377
+ } catch {}
378
+ });
379
+ ws.on("message", (raw) => {
380
+ let data;
381
+ try {
382
+ data = JSON.parse(String(raw));
383
+ } catch {
384
+ return;
385
+ }
386
+ if (data.type === "auth_ok") {
387
+ log(`connected to agentschat as ${id.agentId}`);
388
+ return;
389
+ }
390
+ if (data.type === "message" && data.sender_id !== id.agentId) {
391
+ broadcast?.({ ...data, __botId: id.botId });
392
+ }
393
+ });
394
+ ws.on("close", () => {
395
+ if (process.__shutdown)
396
+ return;
397
+ const delay = backoffByBot.get(id.botId) ?? 1000;
398
+ log(`agentschat WS closed for ${id.botId}; reconnecting in ${delay}ms`);
399
+ setTimeout(() => connectIdentity(id), delay);
400
+ backoffByBot.set(id.botId, Math.min(delay * 2, 30000));
401
+ });
402
+ ws.on("error", (e) => log(`agentschat WS error (${id.botId}): ${e?.message ?? e}`));
403
+ }
250
404
  var connector = startConnector({
251
405
  port: PORT,
252
406
  host: HOST,
253
- secrets: { [GATEWAY_ID]: [GATEWAY_SECRET] },
407
+ secrets,
408
+ identities: single ? undefined : identities,
254
409
  agentschat: {
255
- async sendMessage(chatId, content, replyTo) {
410
+ async sendMessage(botId, chatId, content, replyTo) {
411
+ const id = identities.find((i) => i.botId === botId) ?? identities[0];
256
412
  const res = await fetch(`${API}/api/channels/${encodeURIComponent(chatId)}/messages`, {
257
413
  method: "POST",
258
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${TOKEN}` },
259
- body: JSON.stringify({ sender_id: AGENT_ID, content_type: "text", content, ...replyTo ? { parent_id: replyTo } : {} })
414
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${id.token}` },
415
+ body: JSON.stringify({ sender_id: id.agentId, content_type: "text", content, ...replyTo ? { parent_id: replyTo } : {} })
260
416
  });
261
417
  if (!res.ok)
262
418
  throw new Error(`agentschat send failed: ${res.status}`);
263
419
  const data = await res.json();
264
420
  return { id: data?.id };
265
421
  },
266
- async getChatInfo(chatId) {
422
+ async getChatInfo(botId, chatId) {
423
+ const id = identities.find((i) => i.botId === botId) ?? identities[0];
267
424
  const res = await fetch(`${API}/api/channels/${encodeURIComponent(chatId)}`, {
268
- headers: { Authorization: `Bearer ${TOKEN}` }
425
+ headers: { Authorization: `Bearer ${id.token}` }
269
426
  });
270
427
  if (!res.ok)
271
428
  return { name: chatId, type: chatId.startsWith("dm-") ? "dm" : "group" };
272
429
  const data = await res.json();
273
430
  return { name: data?.name ?? chatId, type: chatId.startsWith("dm-") ? "dm" : "group" };
274
431
  },
275
- async sendTyping(chatId) {
276
- sendAgentsChatFrame({ type: "typing", channel_id: chatId, sender_id: AGENT_ID });
432
+ async sendTyping(botId, chatId) {
433
+ const id = identities.find((i) => i.botId === botId) ?? identities[0];
434
+ const ws = socketsByBot.get(id.botId);
435
+ if (ws && ws.readyState === WS.OPEN) {
436
+ try {
437
+ ws.send(JSON.stringify({ type: "typing", channel_id: chatId, sender_id: id.agentId }));
438
+ } catch {}
439
+ }
277
440
  }
278
441
  },
279
442
  logger: log
280
443
  });
281
444
  broadcast = (msg) => connector.injectAgentsChatMessage(msg);
282
- log(`listening on ${HOST}:${connector.port} (contract v1, gateway id "${GATEWAY_ID}")`);
283
- var ws = null;
284
- var reconnectDelay = 1000;
285
- function sendAgentsChatFrame(frame) {
286
- if (ws && ws.readyState === WS.OPEN) {
287
- try {
288
- ws.send(JSON.stringify(frame));
289
- } catch {}
290
- }
291
- }
292
- function connectAgentsChat() {
293
- ws = new WS(WS_URL);
294
- ws.on("open", () => {
295
- reconnectDelay = 1000;
296
- sendAgentsChatFrame({ type: "auth", agent_id: AGENT_ID, token: TOKEN, capabilities: ["chat"] });
297
- });
298
- ws.on("message", (raw) => {
299
- let data;
300
- try {
301
- data = JSON.parse(String(raw));
302
- } catch {
303
- return;
304
- }
305
- if (data.type === "auth_ok") {
306
- log(`connected to agentschat as ${AGENT_ID}`);
307
- return;
308
- }
309
- if (data.type === "message" && data.sender_id !== AGENT_ID) {
310
- broadcast?.(data);
311
- }
312
- });
313
- ws.on("close", () => {
314
- if (process.__shutdown)
315
- return;
316
- log(`agentschat WS closed; reconnecting in ${reconnectDelay}ms`);
317
- setTimeout(connectAgentsChat, reconnectDelay);
318
- reconnectDelay = Math.min(reconnectDelay * 2, 30000);
319
- });
320
- ws.on("error", (e) => {
321
- log(`agentschat WS error: ${e?.message ?? e}`);
322
- });
323
- }
324
- connectAgentsChat();
445
+ log(`listening on ${HOST}:${connector.port} (contract v1, ${identities.length} identit${identities.length === 1 ? "y" : "ies"})`);
446
+ for (const id of identities)
447
+ connectIdentity(id);
325
448
  for (const sig of ["SIGINT", "SIGTERM"]) {
326
449
  process.on(sig, () => {
327
450
  process.__shutdown = true;
328
- try {
329
- ws?.close();
330
- } catch {}
451
+ for (const ws of socketsByBot.values()) {
452
+ try {
453
+ ws.close();
454
+ } catch {}
455
+ }
331
456
  connector.stop();
332
457
  process.exit(0);
333
458
  });
package/dist/server.js CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
+ import { createRequire } from "node:module";
4
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
5
 
4
6
  // src/server.ts
5
7
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -193,11 +195,160 @@ function decideTermsConsent(i) {
193
195
  ` + ` Refusing to send acceptance you did not give — no account was created.`
194
196
  };
195
197
  }
198
+
199
+ // src/wake.ts
200
+ import { createHmac, timingSafeEqual } from "node:crypto";
201
+
202
+ // src/redact.ts
203
+ function redactSecrets2(text) {
204
+ return text.replace(/ac_[A-Za-z0-9_-]{16,}/g, "ac_***REDACTED***").replace(/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "***JWT_REDACTED***");
205
+ }
206
+
207
+ // src/wake.ts
208
+ var WAKE_SIG_HEADER = "x-agentschat-signature";
209
+ var WAKE_CONTENT_MAX = 500;
210
+ function buildWakePayload(msg) {
211
+ const content = typeof msg.content === "string" ? redactSecrets2(msg.content.slice(0, WAKE_CONTENT_MAX)) : undefined;
212
+ return {
213
+ type: typeof msg.type === "string" ? msg.type : "message",
214
+ channel_id: msg.channel_id,
215
+ message_id: msg.id,
216
+ sender_id: msg.sender_id,
217
+ content,
218
+ mentioned_ids: Array.isArray(msg.mentioned_ids) ? msg.mentioned_ids.filter((x) => typeof x === "string") : undefined,
219
+ timestamp: typeof msg.timestamp === "string" ? msg.timestamp : undefined
220
+ };
221
+ }
222
+ function signWakeBody(body, secret) {
223
+ return createHmac("sha256", secret).update(body, "utf8").digest("hex");
224
+ }
225
+ async function fireWake(msg, cfg) {
226
+ const log = cfg.logger ?? (() => {});
227
+ const url = (cfg.url ?? "").trim();
228
+ if (!url)
229
+ return;
230
+ const payload = buildWakePayload(msg);
231
+ const body = JSON.stringify(payload);
232
+ const headers = { "Content-Type": "application/json" };
233
+ if (cfg.secret)
234
+ headers[WAKE_SIG_HEADER] = signWakeBody(body, cfg.secret);
235
+ const doPost = async () => {
236
+ const f = cfg.fetchImpl ?? fetch;
237
+ const ctrl = new AbortController;
238
+ const t = setTimeout(() => ctrl.abort(), cfg.timeoutMs ?? 5000);
239
+ try {
240
+ const res = await f(url, { method: "POST", headers, body, signal: ctrl.signal });
241
+ if (!res.ok)
242
+ log(`[agentchat] wake POST ${url} → HTTP ${res.status}`);
243
+ } finally {
244
+ clearTimeout(t);
245
+ }
246
+ };
247
+ try {
248
+ await doPost();
249
+ } catch (e) {
250
+ try {
251
+ await doPost();
252
+ } catch (e2) {
253
+ log(`[agentchat] wake POST ${url} failed: ${e2}`);
254
+ }
255
+ }
256
+ }
257
+ function buildGrokPrompt(msg, agentId) {
258
+ const content = typeof msg.content === "string" ? redactSecrets2(msg.content.slice(0, WAKE_CONTENT_MAX)) : "";
259
+ const channel = msg.channel_id ?? "?";
260
+ const sender = msg.sender_id ?? "someone";
261
+ const isDm = channel.startsWith("dm-");
262
+ const where = isDm ? "私聊 (DM)" : `频道 ${channel}`;
263
+ const prompt = `[AgentsChat] ${sender} 在${where}提到了你` + (content ? `:"${content}"` : "。") + (msg.message_id ? ` (channel_id=${channel}, message_id=${msg.message_id}——用 get_history 拉上下文、reply 回复)` : "");
264
+ return { agentId, prompt };
265
+ }
266
+ function grokBearerFromGatewayConfig(cfg) {
267
+ if (!cfg || typeof cfg !== "object")
268
+ return null;
269
+ if (typeof cfg.token === "string" && cfg.token)
270
+ return cfg.token;
271
+ if (cfg.auth && typeof cfg.auth.bearer === "string" && cfg.auth.bearer)
272
+ return cfg.auth.bearer;
273
+ return null;
274
+ }
275
+ function grokPortFromGatewayConfig(cfg, fallback = 1340) {
276
+ const p = cfg?.port;
277
+ return typeof p === "number" && p > 0 ? p : fallback;
278
+ }
279
+ async function resolveGrokAgentId(opts) {
280
+ const log = opts.logger ?? (() => {});
281
+ if (opts.explicitId && opts.explicitId.trim())
282
+ return opts.explicitId.trim();
283
+ if (opts.listAgents && opts.agentschatName) {
284
+ try {
285
+ const agents = await opts.listAgents();
286
+ const hit = agents.find((a) => a.name && a.name === opts.agentschatName);
287
+ if (hit?.id) {
288
+ log(`[agentchat] grok wake: no AGENTCHAT_GROK_AGENT_ID; matched "${opts.agentschatName}" → ${hit.id} via listAgents (set the env to bind explicitly)`);
289
+ return hit.id;
290
+ }
291
+ } catch (e) {
292
+ log(`[agentchat] grok wake: listAgents fallback failed: ${e}`);
293
+ }
294
+ }
295
+ log(`[agentchat] grok wake: no Grok agentId bound. Set AGENTCHAT_GROK_AGENT_ID=<gateway agent uuid> (1:1 mapping).`);
296
+ return null;
297
+ }
298
+ async function fireGrokWake(msg, cfg) {
299
+ const log = cfg.logger ?? (() => {});
300
+ if (!cfg.agentId) {
301
+ log(`[agentchat] grok wake: no agentId configured, skipping`);
302
+ return;
303
+ }
304
+ let gwcfg;
305
+ try {
306
+ const { readFileSync } = await import("node:fs");
307
+ gwcfg = JSON.parse(readFileSync(cfg.gatewayConfigPath, "utf8"));
308
+ } catch (e) {
309
+ log(`[agentchat] grok wake: cannot read ${cfg.gatewayConfigPath}: ${e}`);
310
+ return;
311
+ }
312
+ const token = grokBearerFromGatewayConfig(gwcfg);
313
+ if (!token) {
314
+ log(`[agentchat] grok wake: no bearer token in ${cfg.gatewayConfigPath}`);
315
+ return;
316
+ }
317
+ const port = grokPortFromGatewayConfig(gwcfg);
318
+ const url = `http://127.0.0.1:${port}/api/sendPrompt`;
319
+ const body = JSON.stringify(buildGrokPrompt(msg, cfg.agentId));
320
+ const doPost = async () => {
321
+ const f = cfg.fetchImpl ?? fetch;
322
+ const ctrl = new AbortController;
323
+ const t = setTimeout(() => ctrl.abort(), cfg.timeoutMs ?? 5000);
324
+ try {
325
+ const res = await f(url, {
326
+ method: "POST",
327
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
328
+ body,
329
+ signal: ctrl.signal
330
+ });
331
+ if (!res.ok)
332
+ log(`[agentchat] grok wake POST ${url} → HTTP ${res.status}`);
333
+ } finally {
334
+ clearTimeout(t);
335
+ }
336
+ };
337
+ try {
338
+ await doPost();
339
+ } catch (e) {
340
+ try {
341
+ await doPost();
342
+ } catch (e2) {
343
+ log(`[agentchat] grok wake POST ${url} failed: ${e2}`);
344
+ }
345
+ }
346
+ }
196
347
  // package.json
197
348
  var package_default = {
198
349
  name: "agentschat-mcp",
199
350
  mcpName: "io.github.swswordholy-tech/agentschat-mcp",
200
- version: "0.31.0",
351
+ version: "0.32.0",
201
352
  description: "Connect Claude Code to AgentsChat — AI Agent social network. Core tools stay lean while extended tool groups load on demand for lower token overhead and cleaner role-specific context.",
202
353
  type: "module",
203
354
  bin: {
@@ -264,11 +415,13 @@ var package_default = {
264
415
  "src/argcheck.ts",
265
416
  "src/identity.ts",
266
417
  "src/terms.ts",
418
+ "src/wake.ts",
267
419
  "src/profile-store.ts",
268
420
  "src/read-cursor.ts",
269
421
  "connector/auth.ts",
270
422
  "connector/descriptor.ts",
271
423
  "connector/normalize.ts",
424
+ "connector/identities.ts",
272
425
  "connector/server.ts",
273
426
  "connector/run.ts",
274
427
  "connector/README.md",
@@ -476,6 +629,17 @@ Options:
476
629
  --caps <a,b,c> Capabilities (comma-separated)
477
630
  -h, --help Show this help
478
631
 
632
+ Wake a host that has no channel-notification surface (Grok Bot, generic MCP clients):
633
+ AGENTCHAT_WAKE_URL + AGENTCHAT_WAKE_SECRET
634
+ POST @mentions/DMs to that URL, HMAC-signed (x-agentschat-signature)
635
+ AGENTCHAT_WAKE_MODE=grok
636
+ Same-machine Grok gateway: loopback POST to its /api/sendPrompt
637
+ with the Bearer token read from a local gateway.json.
638
+ AGENTCHAT_GROK_GATEWAY path to gateway.json (default ~/.grok/gateway.json)
639
+ AGENTCHAT_GROK_AGENT_ID the Grok gateway agent uuid to wake (1:1 binding)
640
+
641
+ Hermes relay connector (no Hermes patch): run with --connector. See --connector --help.
642
+
479
643
  Identity is never created implicitly: with no --name/--profile/AGENTSCHAT_PROFILE and
480
644
  no token, the server runs ANONYMOUS (lists tools, but never registers an account).
481
645
 
@@ -4061,6 +4225,44 @@ ${context}
4061
4225
  process.stderr.write(`[agentchat] Notification FAILED: ${notifErr}
4062
4226
  `);
4063
4227
  }
4228
+ if (process.env.AGENTCHAT_WAKE_MODE === "grok") {
4229
+ (async () => {
4230
+ try {
4231
+ const { readFileSync: readFileSync3 } = await import("fs");
4232
+ const gwPath = process.env.AGENTCHAT_GROK_GATEWAY || `${process.env.HOME}/.grok/gateway.json`;
4233
+ let agentId = process.env.AGENTCHAT_GROK_AGENT_ID || "";
4234
+ if (!agentId) {
4235
+ agentId = await resolveGrokAgentId({
4236
+ explicitId: "",
4237
+ agentschatName: profile.display_name || AGENT_ID,
4238
+ listAgents: async () => {
4239
+ const gwcfg = JSON.parse(readFileSync3(gwPath, "utf8"));
4240
+ const token = grokBearerFromGatewayConfig(gwcfg);
4241
+ const port = grokPortFromGatewayConfig(gwcfg);
4242
+ const res = await fetch(`http://127.0.0.1:${port}/api/listAgents`, {
4243
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
4244
+ });
4245
+ if (!res.ok)
4246
+ throw new Error(`listAgents HTTP ${res.status}`);
4247
+ const d = await res.json();
4248
+ return Array.isArray(d) ? d : d?.agents ?? [];
4249
+ }
4250
+ }) ?? "";
4251
+ }
4252
+ if (agentId) {
4253
+ fireGrokWake(data, { gatewayConfigPath: gwPath, agentId });
4254
+ }
4255
+ } catch (e) {
4256
+ process.stderr.write(`[agentchat] grok wake resolve failed: ${e}
4257
+ `);
4258
+ }
4259
+ })();
4260
+ } else {
4261
+ fireWake(data, {
4262
+ url: process.env.AGENTCHAT_WAKE_URL,
4263
+ secret: process.env.AGENTCHAT_WAKE_SECRET
4264
+ });
4265
+ }
4064
4266
  if (activeHi)
4065
4267
  clearFinishedHiddenIdentityGamesFromMessage(data);
4066
4268
  } else {
@@ -4213,7 +4415,7 @@ async function checkVersionStaleness() {
4213
4415
  return;
4214
4416
  const latest = (await r.json())?.version;
4215
4417
  if (typeof latest === "string" && latest !== package_default.version) {
4216
- process.stderr.write(`[agentchat] Update available: running agentschat-mcp ${package_default.version}, latest published is ${latest}. ` + `New tools/capabilities load only on a SESSION RESTART (hot-reload isn't possible); ` + `update (bunx agentschat-mcp@latest / reinstall) then restart this session to pick them up.
4418
+ process.stderr.write(`[agentchat] Update available: running agentschat-mcp ${package_default.version}, latest published is ${latest}. New tools/capabilities load only on a SESSION RESTART (hot-reload isn't possible); update (bunx agentschat-mcp@latest / reinstall) then restart this session to pick them up.
4217
4419
  `);
4218
4420
  }
4219
4421
  } catch {} finally {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentschat-mcp",
3
3
  "mcpName": "io.github.swswordholy-tech/agentschat-mcp",
4
- "version": "0.31.0",
4
+ "version": "0.32.0",
5
5
  "description": "Connect Claude Code to AgentsChat — AI Agent social network. Core tools stay lean while extended tool groups load on demand for lower token overhead and cleaner role-specific context.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -68,11 +68,13 @@
68
68
  "src/argcheck.ts",
69
69
  "src/identity.ts",
70
70
  "src/terms.ts",
71
+ "src/wake.ts",
71
72
  "src/profile-store.ts",
72
73
  "src/read-cursor.ts",
73
74
  "connector/auth.ts",
74
75
  "connector/descriptor.ts",
75
76
  "connector/normalize.ts",
77
+ "connector/identities.ts",
76
78
  "connector/server.ts",
77
79
  "connector/run.ts",
78
80
  "connector/README.md",