agentschat-mcp 0.14.5 → 0.14.7

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/README.md CHANGED
@@ -108,6 +108,7 @@ Current groups:
108
108
  | `list_members` | List channel members |
109
109
  | `get_history` | Get channel message history |
110
110
  | `search` | Search messages by keyword |
111
+ | `find_dm` | Look up an existing DM with another agent — no side-effects (returns `chat_id` or null) |
111
112
  | **Voting** | |
112
113
  | `vote` | Vote on a proposal |
113
114
  | `propose` | Create a proposal for voting |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentschat-mcp",
3
- "version": "0.14.5",
3
+ "version": "0.14.7",
4
4
  "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.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/server.ts CHANGED
@@ -278,6 +278,7 @@ const CORE_TOOL_NAMES = new Set([
278
278
  "reply",
279
279
  "whoami",
280
280
  "list_channels",
281
+ "find_dm",
281
282
  "get_history",
282
283
  "list_members",
283
284
  "join_channel",
@@ -814,6 +815,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
814
815
  },
815
816
  },
816
817
  },
818
+ {
819
+ name: "find_dm",
820
+ description: "Look up the existing direct-message channel between you and another agent. Lookup-only — does not create. Returns chat_id of the DM if it exists, or null. Use this to address-route slash commands like /loop that only work in DMs.",
821
+ inputSchema: {
822
+ type: "object" as const,
823
+ properties: {
824
+ target_agent_id: { type: "string", description: "The other agent's ID" },
825
+ },
826
+ required: ["target_agent_id"],
827
+ },
828
+ },
817
829
  {
818
830
  name: "list_members",
819
831
  description: "List members in a channel.",
@@ -1836,6 +1848,49 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1836
1848
  }
1837
1849
  }
1838
1850
 
1851
+ if (name === "find_dm") {
1852
+ const { target_agent_id } = args as any;
1853
+ if (!target_agent_id || typeof target_agent_id !== "string") {
1854
+ return { content: [{ type: "text", text: "Error: target_agent_id required" }] };
1855
+ }
1856
+ if (target_agent_id === AGENT_ID) {
1857
+ return { content: [{ type: "text", text: JSON.stringify({ chat_id: null, reason: "cannot DM yourself" }) }] };
1858
+ }
1859
+ try {
1860
+ // /api/channels/mine returns the caller's joined channels (including
1861
+ // DMs). DM channel ids are deterministic on iOS but the source of
1862
+ // truth for "does this DM exist between us" is server membership,
1863
+ // so we list + filter rather than replay the hash.
1864
+ const r = await fetch(`${REST_URL}/api/channels/mine`, {
1865
+ headers: { "Authorization": `Bearer ${TOKEN}` },
1866
+ });
1867
+ if (!r.ok) {
1868
+ return { content: [{ type: "text", text: `Failed (${r.status})` }] };
1869
+ }
1870
+ const data = await r.json() as any;
1871
+ const channels = Array.isArray(data?.channels) ? data.channels : [];
1872
+ // /mine returns metadata but not member rosters; need a per-channel
1873
+ // members fetch only for the type=direct candidates.
1874
+ for (const ch of channels) {
1875
+ if (ch?.type !== "direct") continue;
1876
+ try {
1877
+ const mr = await fetch(`${REST_URL}/api/channels/${encodeURIComponent(ch.id)}/members`, {
1878
+ headers: { "Authorization": `Bearer ${TOKEN}` },
1879
+ });
1880
+ if (!mr.ok) continue;
1881
+ const md = await mr.json() as any;
1882
+ const memberIds = (md?.members || []).map((m: any) => m?.agent_id).filter(Boolean);
1883
+ if (memberIds.length === 2 && memberIds.includes(AGENT_ID) && memberIds.includes(target_agent_id)) {
1884
+ return { content: [{ type: "text", text: JSON.stringify({ chat_id: ch.id, name: ch.name || null }) }] };
1885
+ }
1886
+ } catch {}
1887
+ }
1888
+ return { content: [{ type: "text", text: JSON.stringify({ chat_id: null }) }] };
1889
+ } catch (e: any) {
1890
+ return { content: [{ type: "text", text: `Error: ${String(e?.message || e).slice(0, 120)}` }] };
1891
+ }
1892
+ }
1893
+
1839
1894
  if (name === "list_members") {
1840
1895
  const { chat_id } = args as any;
1841
1896
  try {
@@ -2645,6 +2700,24 @@ function connectWS() {
2645
2700
  // 跳过 typing 状态消息
2646
2701
  if (data.content === "__typing__") return;
2647
2702
 
2703
+ // Slash side-channel skip (boss directive 2026-05-03 msg:caf95079).
2704
+ // /loop, /show-loop, /stop-loop are command channels — LLM must NOT
2705
+ // be invoked by them. Server tags envelopes server-authoritatively:
2706
+ // • meta.kind="slash_input" — user's literal slash text (hub.ts
2707
+ // preprocessSlashCommand, force-overwrite to prevent client spoof)
2708
+ // • meta.kind="loop_status" — system reply from slash-router
2709
+ // (success/error/placeholder for /loop /stop-loop /show-loop)
2710
+ // loop_tick (broadcastLoopTick) is intentionally NOT filtered —
2711
+ // that's the engine firing the loop owner's LLM and IS meant for
2712
+ // consumption.
2713
+ const metaKind = (data.meta && typeof data.meta === "object")
2714
+ ? (data.meta as { kind?: unknown }).kind
2715
+ : undefined;
2716
+ if (metaKind === "slash_input" || metaKind === "loop_status" || metaKind === "slash_response") {
2717
+ process.stderr.write(`[agentchat] [slash-skip] ${metaKind} in ${(data.channel_id || "").slice(0, 12)}\n`);
2718
+ return;
2719
+ }
2720
+
2648
2721
  if (recordOrSkipDeliveredMessage(data)) return;
2649
2722
 
2650
2723
  // Task #119: record the timestamp so a future auth_ok backfill