agentschat-mcp 0.32.3 → 0.33.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.
@@ -50,6 +50,14 @@ A's messages never reach or send as identity B (an un-hello'd identity egress is
50
50
  rejected per the contract's advertised-set check, D-Q1.5b.1; unaddressed inbound
51
51
  is dropped, never broadcast). Single-tenant env is the N=1 case, unchanged.
52
52
 
53
+ **Inbound gating (all modes):** the AgentsChat WS pushes every message of every
54
+ joined channel. The connector injects into the gateway only what is ADDRESSED to
55
+ an identity — DMs always, group messages only when the body @mentions it (same
56
+ gate the MCP path uses: `isDM || isMentioned`). On an @-mention it also attaches
57
+ the channel history since that identity was last addressed as the wire `context`
58
+ field, which upstream renders into the event's channel context — the agent sees
59
+ the conversation between its mentions without paying tokens for all of it.
60
+
53
61
  Point Hermes at it by setting `GATEWAY_RELAY_URL=ws://<host>:8765/relay` (the gateway
54
62
  then upgrades with `Authorization: Bearer <HMAC token>` derived from the shared
55
63
  secret — see `gateway/relay/auth.py`).
@@ -60,7 +68,7 @@ secret — see `gateway/relay/auth.py`).
60
68
  |---|---|---|
61
69
  | WS upgrade auth (HMAC-SHA256, close 4401) | gateway → connector | ✅ |
62
70
  | `hello` → `descriptor` handshake (one per identity in multiplex) | gateway ↔ connector | ✅ |
63
- | `inbound` (agentschat message → `MessageEvent`, routed per identity, `source.profile` tagged) | connector → gateway | ✅ |
71
+ | `inbound` — DM always; group only on content @mention; @-mentions carry a `context` window (history since last addressed) → `MessageEvent` w/ `source.profile` | connector → gateway | ✅ |
64
72
  | `outbound` op `send` → `outbound_result` (per-identity token, advertised-set checked) | gateway → connector | ✅ |
65
73
  | `outbound` op `typing` | gateway → connector | ✅ |
66
74
  | `outbound` op `get_chat_info` | gateway → connector | ✅ |
@@ -14,6 +14,8 @@
14
14
  * does not change single-tenant behavior.
15
15
  */
16
16
 
17
+ import { matchesMention } from "../src/mentions.ts";
18
+
17
19
  export interface Identity {
18
20
  /** The relay hello botId — the agentschat agent_id this identity fronts. */
19
21
  botId: string;
@@ -60,6 +62,8 @@ export class IdentityTable {
60
62
  export interface InboundContext {
61
63
  channel_id?: string;
62
64
  mentioned_ids?: string[];
65
+ /** Message body — the PRIMARY group-mention signal (see routeInbound). */
66
+ content?: string;
63
67
  /** For DM channels: which identity owns this DM (the connector tracks dm ownership). */
64
68
  dmOwnerBotId?: string;
65
69
  }
@@ -72,18 +76,34 @@ export interface InboundContext {
72
76
  * Routing rule: a DM goes to its owning identity; a group/channel message goes to
73
77
  * the identity it @mentions. A message mentioning no fronted identity (or in a DM
74
78
  * owned by none) routes to no one.
79
+ *
80
+ * Group mention detection is CONTENT-based (matchesMention over the message body):
81
+ * the agentschat WS pushes every message of a joined channel WITHOUT a mentioned_ids
82
+ * annotation, so the mention gate the MCP path uses (isDM || isMentioned) must be
83
+ * reproduced here — otherwise every joined-channel message would be injected into
84
+ * the agent's session and burn its tokens on chatter not addressed to it.
85
+ * `mentioned_ids` is still honored when a host provides it (explicit signal wins).
75
86
  */
76
87
  export function routeInbound(table: IdentityTable, ctx: InboundContext): Identity | null {
77
88
  // DM: route to the identity that owns the DM channel.
78
89
  if (ctx.channel_id?.startsWith("dm-")) {
79
90
  return ctx.dmOwnerBotId ? table.forBot(ctx.dmOwnerBotId) : null;
80
91
  }
81
- // Group/channel: route to a fronted identity that was @mentioned.
92
+ // Group/channel: an explicit mentioned_ids annotation wins when present.
82
93
  const mentioned = Array.isArray(ctx.mentioned_ids) ? ctx.mentioned_ids : [];
83
94
  for (const mid of mentioned) {
84
95
  const id = table.forBot(mid);
85
96
  if (id) return id;
86
97
  }
98
+ // Otherwise content-based: which fronted identity does the body @mention?
99
+ // (First match wins — a message @-ing two fronted identities goes to the first;
100
+ // the other sees it when ITS mention arrives or via channel context.)
101
+ const content = ctx.content ?? "";
102
+ if (content) {
103
+ for (const id of table.all()) {
104
+ if (matchesMention(content, id.agentId) || matchesMention(content, id.botId)) return id;
105
+ }
106
+ }
87
107
  return null;
88
108
  }
89
109
 
@@ -33,6 +33,13 @@ export interface WireEvent {
33
33
  message_type: string;
34
34
  message_id?: string;
35
35
  reply_to_message_id?: string;
36
+ /**
37
+ * Surrounding channel context (the "since you were last addressed" window the
38
+ * connector attaches on @-mentions). Upstream `_render_relay_context` renders
39
+ * it into the event's channel_context: oldest→newest `{text, source:{user_name
40
+ * |user_id}}` items. Absent on DMs and when there's nothing worth attaching.
41
+ */
42
+ context?: Array<{ text: string; source?: { user_name?: string; user_id?: string } }>;
36
43
  source: {
37
44
  platform: string;
38
45
  chat_id: string;
package/connector/run.ts CHANGED
@@ -24,6 +24,7 @@
24
24
  import WS from "ws";
25
25
  import { startConnector } from "./server.ts";
26
26
  import type { Identity } from "./identities.ts";
27
+ import { redactSecrets } from "../src/redact.ts";
27
28
 
28
29
  const log = (m: string) => process.stderr.write(`[agentschat-connector] ${m}\n`);
29
30
 
@@ -130,7 +131,9 @@ const connector = startConnector({
130
131
  port: PORT,
131
132
  host: HOST,
132
133
  secrets,
133
- identities: single ? undefined : identities, // single-tenant → derived default identity
134
+ // Always pass the real identity table (even N=1) so botId = the real agentschat
135
+ // agent_id and content-based @mention routing works in single-tenant too.
136
+ identities,
134
137
  agentschat: {
135
138
  async sendMessage(botId, chatId, content, replyTo) {
136
139
  const id = identities.find((i) => i.botId === botId) ?? identities[0];
@@ -159,6 +162,28 @@ const connector = startConnector({
159
162
  try { ws.send(JSON.stringify({ type: "typing", channel_id: chatId, sender_id: id.agentId })); } catch {}
160
163
  }
161
164
  },
165
+ // The "since you were last @'d" window: recent channel history after sinceTs,
166
+ // oldest→newest, trigger message excluded, secrets redacted (a group channel is
167
+ // untrusted content — never forward a leaked key downstream). Best-effort: any
168
+ // failure returns null and the addressed message still delivers without it.
169
+ async getChannelContext(botId, chatId, sinceTs, excludeId) {
170
+ const id = identities.find((i) => i.botId === botId) ?? identities[0];
171
+ const res = await fetch(`${API}/api/channels/${encodeURIComponent(chatId)}/messages?limit=50`, {
172
+ headers: { Authorization: `Bearer ${id.token}` },
173
+ });
174
+ if (!res.ok) return null;
175
+ const msgs = (((await res.json()) as any)?.messages ?? [])
176
+ .filter((m: any) => m?.content && m.content !== "__typing__" && m?.id !== excludeId)
177
+ .sort((a: any, b: any) => String(a.timestamp ?? "").localeCompare(String(b.timestamp ?? "")));
178
+ const windowed = sinceTs ? msgs.filter((m: any) => String(m.timestamp ?? "") > sinceTs) : msgs;
179
+ const tail = windowed.slice(-10);
180
+ if (!tail.length) return null;
181
+ return tail.map((m: any) => ({
182
+ text: redactSecrets(String(m.content)).slice(0, 500),
183
+ user_name: m.sender_name ?? m.sender_id,
184
+ user_id: m.sender_id,
185
+ }));
186
+ },
162
187
  },
163
188
  logger: log,
164
189
  });
@@ -34,6 +34,16 @@ export interface AgentsChatHooks {
34
34
  sendMessage(botId: string, chatId: string, content: string, replyTo?: string): Promise<{ id?: string }>;
35
35
  getChatInfo(botId: string, chatId: string): Promise<{ name?: string; type?: string }>;
36
36
  sendTyping?(botId: string, chatId: string): Promise<void>;
37
+ /**
38
+ * Recent channel messages to attach as `context` when an @-mention arrives —
39
+ * the "what happened since the last time I was addressed" window. `sinceTs` is
40
+ * the timestamp of the last message addressed to this identity in this channel
41
+ * (undefined = never). `excludeId` is the trigger message's own id (already
42
+ * delivered as the event body — don't repeat it in the context). Return null/
43
+ * [] when there is nothing worth attaching. Best-effort: failures must not
44
+ * block delivery of the addressed message itself.
45
+ */
46
+ getChannelContext?(botId: string, chatId: string, sinceTs?: string, excludeId?: string): Promise<Array<{ text: string; user_name?: string; user_id?: string }> | null>;
37
47
  }
38
48
 
39
49
  /** Legacy single-tenant hook shape (no botId first arg) — adapted to the per-identity one. */
@@ -41,6 +51,7 @@ interface LegacyHooks {
41
51
  sendMessage(chatId: string, content: string, replyTo?: string): Promise<{ id?: string }>;
42
52
  getChatInfo(chatId: string): Promise<{ name?: string; type?: string }>;
43
53
  sendTyping?(chatId: string): Promise<void>;
54
+ getChannelContext?(chatId: string, sinceTs?: string, excludeId?: string): Promise<Array<{ text: string; user_name?: string; user_id?: string }> | null>;
44
55
  }
45
56
 
46
57
  export interface ConnectorConfig {
@@ -64,7 +75,7 @@ export interface ConnectorHandle {
64
75
  port: number;
65
76
  stop(): void;
66
77
  /** Push an agentschat message to the gateway socket(s) fronting its addressed identity. */
67
- injectAgentsChatMessage(msg: AgentsChatMessage): void;
78
+ injectAgentsChatMessage(msg: AgentsChatMessage): void | Promise<void>;
68
79
  connections(): number;
69
80
  }
70
81
 
@@ -79,20 +90,31 @@ interface GatewayConn {
79
90
  export function startConnector(config: ConnectorConfig): ConnectorHandle {
80
91
  const log = config.logger ?? (() => {});
81
92
  const descriptor = buildDescriptor(config.descriptor);
82
- const single = !config.identities || config.identities.length === 0;
93
+ // "legacy" = no identity table configured (the pre-multiplex test/embedding
94
+ // path): a single derived identity and chatId-first hooks. "single" = the
95
+ // table holds exactly one identity (legacy OR a one-entry RELAY_IDENTITIES) —
96
+ // it gates hello acceptance and outbound identity resolution, NOT inbound
97
+ // group forwarding (unaddressed group chatter is dropped in every mode).
98
+ const legacy = !config.identities || config.identities.length === 0;
83
99
  const table = new IdentityTable(
84
- single ? [{ botId: "default", agentId: "default", token: "", gatewayId: "", secret: "" }] : config.identities!,
100
+ legacy ? [{ botId: "default", agentId: "default", token: "", gatewayId: "", secret: "" }] : config.identities!,
85
101
  );
102
+ const single = table.isSingle();
86
103
  // Normalize hooks to the per-identity shape. Legacy single-tenant hooks take
87
104
  // (chatId, ...); wrap them to ignore the botId. Per-identity hooks take botId first.
88
- const hooks: AgentsChatHooks = single
105
+ const hooks: AgentsChatHooks = legacy
89
106
  ? {
90
107
  sendMessage: (_b, chatId, content, replyTo) => (config.agentschat as LegacyHooks).sendMessage(chatId, content, replyTo),
91
108
  getChatInfo: (_b, chatId) => (config.agentschat as LegacyHooks).getChatInfo(chatId),
92
109
  sendTyping: (_b, chatId) => (config.agentschat as LegacyHooks).sendTyping?.(chatId) ?? Promise.resolve(),
110
+ getChannelContext: (_b, chatId, sinceTs, excludeId) =>
111
+ (config.agentschat as LegacyHooks).getChannelContext?.(chatId, sinceTs, excludeId) ?? Promise.resolve(null),
93
112
  }
94
113
  : (config.agentschat as AgentsChatHooks);
95
114
  const sockets = new Set<GatewayConn>();
115
+ // `${botId}:${chatId}` → timestamp of the last message ADDRESSED to that
116
+ // identity in that channel. Drives the getChannelContext `sinceTs` window.
117
+ const lastAddressed = new Map<string, string>();
96
118
 
97
119
  const http: HttpServer = createServer((_req, res) => {
98
120
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -238,18 +260,30 @@ export function startConnector(config: ConnectorConfig): ConnectorHandle {
238
260
  wss.close();
239
261
  http.close();
240
262
  },
241
- injectAgentsChatMessage(msg: AgentsChatMessage) {
242
- // Route to the identity this message is addressed to, then only to gateway
263
+ async injectAgentsChatMessage(msg: AgentsChatMessage) {
264
+ // Route to the identity this message is ADDRESSED to, then only to gateway
243
265
  // socket(s) fronting THAT identity — never broadcast across identities.
244
- // The strongest signal is __botId: which identity's agentschat socket the
245
- // message ARRIVED on (agentschat only pushes @mentions/DMs to that agent).
246
- // Fall back to mention/DM-owner routing for hosts that don't tag.
266
+ //
267
+ // DM: always forward. __botId (which identity's agentschat socket it arrived
268
+ // on) is the ownership signal; single-tenant falls back to its one identity.
269
+ // Group: forward ONLY when the body @mentions a fronted identity (content-
270
+ // based — the agentschat WS pushes every message of a joined channel
271
+ // unannotated, and arrival on a socket is NOT an addressing signal). The
272
+ // MCP path's gate is isDM || isMentioned; this reproduces it. Anything
273
+ // unaddressed is dropped — injecting joined-channel chatter into the
274
+ // agent's session would burn its tokens on messages not meant for it.
275
+ const isDm = typeof msg.channel_id === "string" && msg.channel_id.startsWith("dm-");
247
276
  const bySocket = (msg as any).__botId ? table.forBot(String((msg as any).__botId)) : null;
248
- const target = bySocket ?? routeInbound(table, {
249
- channel_id: msg.channel_id,
250
- mentioned_ids: (msg as any).mentioned_ids,
251
- dmOwnerBotId: (msg as any).dm_owner,
252
- }) ?? (table.isSingle() ? table.all()[0] : null);
277
+ const target = isDm
278
+ ? bySocket ?? routeInbound(table, {
279
+ channel_id: msg.channel_id,
280
+ dmOwnerBotId: (msg as any).dm_owner,
281
+ }) ?? (table.isSingle() ? table.all()[0] : null)
282
+ : routeInbound(table, {
283
+ channel_id: msg.channel_id,
284
+ mentioned_ids: (msg as any).mentioned_ids,
285
+ content: msg.content,
286
+ });
253
287
  if (!target) {
254
288
  log(`[connector] inbound unaddressed (channel=${(msg as any).channel_id ?? "?"} mentions=${JSON.stringify((msg as any).mentioned_ids ?? [])}) — dropped`);
255
289
  return;
@@ -258,6 +292,29 @@ export function startConnector(config: ConnectorConfig): ConnectorHandle {
258
292
  if (!event) return;
259
293
  // Tag the fronting identity so the gateway keys the right session/profile.
260
294
  (event.source as any).profile = target.botId;
295
+ if (!isDm) {
296
+ // Attach "what happened since you were last addressed" so the agent gets
297
+ // the conversation BETWEEN its @-mentions without being injected into
298
+ // every unaddressed message. Upstream renders event.context into the
299
+ // event's channel_context ("[Recent channel messages]") — gateway needs
300
+ // no change. Best-effort: a context failure never delays the message.
301
+ const key = `${target.botId}:${msg.channel_id}`;
302
+ const since = lastAddressed.get(key);
303
+ if (hooks.getChannelContext) {
304
+ try {
305
+ const ctx = await hooks.getChannelContext(target.botId, msg.channel_id!, since, msg.id);
306
+ if (ctx && ctx.length) {
307
+ event.context = ctx.slice(-10).map((c) => ({
308
+ text: String(c?.text ?? "").slice(0, 500),
309
+ source: { user_name: c?.user_name, user_id: c?.user_id },
310
+ }));
311
+ }
312
+ } catch (e) {
313
+ log(`[connector] context fetch failed for ${key} (delivering without it): ${e}`);
314
+ }
315
+ }
316
+ if (msg.timestamp) lastAddressed.set(key, msg.timestamp);
317
+ }
261
318
  for (const conn of sockets) {
262
319
  if (conn.fronted.has(target.botId)) send(conn.ws, { type: "inbound", event });
263
320
  }
package/dist/connector.js CHANGED
@@ -104,6 +104,17 @@ function toWireEvent(msg, platform = "agentschat") {
104
104
  };
105
105
  }
106
106
 
107
+ // src/mentions.ts
108
+ function matchesMention(content, agentId) {
109
+ if (!content || !agentId)
110
+ return false;
111
+ if (content.includes(`@${agentId}`))
112
+ return true;
113
+ const idEsc = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
114
+ const displayMentionRe = new RegExp(`@[^(\\n]+\\(${idEsc}\\)`);
115
+ return displayMentionRe.test(content);
116
+ }
117
+
107
118
  // connector/identities.ts
108
119
  class IdentityTable {
109
120
  byBot = new Map;
@@ -138,6 +149,13 @@ function routeInbound(table, ctx) {
138
149
  if (id)
139
150
  return id;
140
151
  }
152
+ const content = ctx.content ?? "";
153
+ if (content) {
154
+ for (const id of table.all()) {
155
+ if (matchesMention(content, id.agentId) || matchesMention(content, id.botId))
156
+ return id;
157
+ }
158
+ }
141
159
  return null;
142
160
  }
143
161
 
@@ -145,14 +163,17 @@ function routeInbound(table, ctx) {
145
163
  function startConnector(config) {
146
164
  const log = config.logger ?? (() => {});
147
165
  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 ? {
166
+ const legacy = !config.identities || config.identities.length === 0;
167
+ const table = new IdentityTable(legacy ? [{ botId: "default", agentId: "default", token: "", gatewayId: "", secret: "" }] : config.identities);
168
+ const single = table.isSingle();
169
+ const hooks = legacy ? {
151
170
  sendMessage: (_b, chatId, content, replyTo) => config.agentschat.sendMessage(chatId, content, replyTo),
152
171
  getChatInfo: (_b, chatId) => config.agentschat.getChatInfo(chatId),
153
- sendTyping: (_b, chatId) => config.agentschat.sendTyping?.(chatId) ?? Promise.resolve()
172
+ sendTyping: (_b, chatId) => config.agentschat.sendTyping?.(chatId) ?? Promise.resolve(),
173
+ getChannelContext: (_b, chatId, sinceTs, excludeId) => config.agentschat.getChannelContext?.(chatId, sinceTs, excludeId) ?? Promise.resolve(null)
154
174
  } : config.agentschat;
155
175
  const sockets = new Set;
176
+ const lastAddressed = new Map;
156
177
  const http = createServer((_req, res) => {
157
178
  res.writeHead(200, { "Content-Type": "application/json" });
158
179
  res.end(JSON.stringify({ ok: true, service: "agentschat-connector", contract_version: 1, identities: table.size }));
@@ -272,13 +293,17 @@ function startConnector(config) {
272
293
  wss.close();
273
294
  http.close();
274
295
  },
275
- injectAgentsChatMessage(msg) {
296
+ async injectAgentsChatMessage(msg) {
297
+ const isDm = typeof msg.channel_id === "string" && msg.channel_id.startsWith("dm-");
276
298
  const bySocket = msg.__botId ? table.forBot(String(msg.__botId)) : null;
277
- const target = bySocket ?? routeInbound(table, {
299
+ const target = isDm ? bySocket ?? routeInbound(table, {
278
300
  channel_id: msg.channel_id,
279
- mentioned_ids: msg.mentioned_ids,
280
301
  dmOwnerBotId: msg.dm_owner
281
- }) ?? (table.isSingle() ? table.all()[0] : null);
302
+ }) ?? (table.isSingle() ? table.all()[0] : null) : routeInbound(table, {
303
+ channel_id: msg.channel_id,
304
+ mentioned_ids: msg.mentioned_ids,
305
+ content: msg.content
306
+ });
282
307
  if (!target) {
283
308
  log(`[connector] inbound unaddressed (channel=${msg.channel_id ?? "?"} mentions=${JSON.stringify(msg.mentioned_ids ?? [])}) — dropped`);
284
309
  return;
@@ -287,6 +312,25 @@ function startConnector(config) {
287
312
  if (!event)
288
313
  return;
289
314
  event.source.profile = target.botId;
315
+ if (!isDm) {
316
+ const key = `${target.botId}:${msg.channel_id}`;
317
+ const since = lastAddressed.get(key);
318
+ if (hooks.getChannelContext) {
319
+ try {
320
+ const ctx = await hooks.getChannelContext(target.botId, msg.channel_id, since, msg.id);
321
+ if (ctx && ctx.length) {
322
+ event.context = ctx.slice(-10).map((c) => ({
323
+ text: String(c?.text ?? "").slice(0, 500),
324
+ source: { user_name: c?.user_name, user_id: c?.user_id }
325
+ }));
326
+ }
327
+ } catch (e) {
328
+ log(`[connector] context fetch failed for ${key} (delivering without it): ${e}`);
329
+ }
330
+ }
331
+ if (msg.timestamp)
332
+ lastAddressed.set(key, msg.timestamp);
333
+ }
290
334
  for (const conn of sockets) {
291
335
  if (conn.fronted.has(target.botId))
292
336
  send(conn.ws, { type: "inbound", event });
@@ -309,6 +353,11 @@ function peekPayload(token) {
309
353
  }
310
354
  }
311
355
 
356
+ // src/redact.ts
357
+ function redactSecrets(text) {
358
+ 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***");
359
+ }
360
+
312
361
  // connector/run.ts
313
362
  var log = (m) => process.stderr.write(`[agentschat-connector] ${m}
314
363
  `);
@@ -405,7 +454,7 @@ var connector = startConnector({
405
454
  port: PORT,
406
455
  host: HOST,
407
456
  secrets,
408
- identities: single ? undefined : identities,
457
+ identities,
409
458
  agentschat: {
410
459
  async sendMessage(botId, chatId, content, replyTo) {
411
460
  const id = identities.find((i) => i.botId === botId) ?? identities[0];
@@ -437,6 +486,24 @@ var connector = startConnector({
437
486
  ws.send(JSON.stringify({ type: "typing", channel_id: chatId, sender_id: id.agentId }));
438
487
  } catch {}
439
488
  }
489
+ },
490
+ async getChannelContext(botId, chatId, sinceTs, excludeId) {
491
+ const id = identities.find((i) => i.botId === botId) ?? identities[0];
492
+ const res = await fetch(`${API}/api/channels/${encodeURIComponent(chatId)}/messages?limit=50`, {
493
+ headers: { Authorization: `Bearer ${id.token}` }
494
+ });
495
+ if (!res.ok)
496
+ return null;
497
+ const msgs = ((await res.json())?.messages ?? []).filter((m) => m?.content && m.content !== "__typing__" && m?.id !== excludeId).sort((a, b) => String(a.timestamp ?? "").localeCompare(String(b.timestamp ?? "")));
498
+ const windowed = sinceTs ? msgs.filter((m) => String(m.timestamp ?? "") > sinceTs) : msgs;
499
+ const tail = windowed.slice(-10);
500
+ if (!tail.length)
501
+ return null;
502
+ return tail.map((m) => ({
503
+ text: redactSecrets(String(m.content)).slice(0, 500),
504
+ user_name: m.sender_name ?? m.sender_id,
505
+ user_id: m.sender_id
506
+ }));
440
507
  }
441
508
  },
442
509
  logger: log
package/dist/server.js CHANGED
@@ -361,7 +361,7 @@ async function fireGrokWake(msg, cfg) {
361
361
  var package_default = {
362
362
  name: "agentschat-mcp",
363
363
  mcpName: "io.github.swswordholy-tech/agentschat-mcp",
364
- version: "0.32.3",
364
+ version: "0.33.0",
365
365
  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.",
366
366
  type: "module",
367
367
  bin: {
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.32.3",
4
+ "version": "0.33.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": {