Agent-to-agent Slack rooms — a group DM (MPIM) holding a human plus two or more NanoClaw sibling bots, where each bot hears the room over its own Socket Mode co
复制下面这句话,粘贴给 Claude Code、Codex、Cursor 等 AI 编程工具,它会读取安装说明并在你确认后完成安装。
请阅读 https://ai.atlankj.com/install/asset/gh-slack-a2a-rooms-45b6abfc6993 ,按照其中的说明把「slack-a2a-rooms」安装到你(当前 AI 工具)中。执行前先告诉我将运行的命令和写入的位置,等我确认。
查看 AI 将读取的安装说明正在读取 GitHub 原文…
内容来自 GitHub 原始文件,由原作者维护。在 GitHub 查看
Lets two or more NanoClaw Slack bots talk to each other — and to a human — in
a shared group DM (MPIM). Empirically established against live Slack:
conversations.open with users=[<human>, <other bot user id>…] works from a
bot token holding mpim:write and returns an is_mpim channel, and bots
receive each other's messages over their own Socket Mode connections as plain
message events with channel_type: "mpim", bot_id set, and subtype
null.
Canonical home. This directory on main is the skill's canonical source —
the setup wizard and any direct apply read it from the checkout. The copy on
the channels branch is a compatibility mirror for older checkouts whose
setup fetches companions from there; edits land here, never there.
Where sibling-bot messages die today. The adapter/Chat-SDK stack's only
bot filter is self-protection (isMe); a sibling bot's message arrives with
isMe: false, isBot: true and flows into the Slack channel's bot-inbound
guard (src/channels/slack-a2a-guard.ts, installed with /add-slack), which
drops all bot-authored inbound at the bridge boundary by default — before the
router, before any sender-approval flow. The guard exposes a single admission
seam, setBotInboundPolicy; this skill registers the policy that opens it up
selectively:
SLACK_A2A_ROOMS: bot-authored inbound passes, attributed
to the user id slack:bot:<bot_id>, under a consecutive-hop limit.Loop safety. After N consecutive bot-authored inbound messages in an A2A
room without a human message (N = SLACK_A2A_MAX_HOPS, default 6), further
bot messages are dropped with a log line until a human speaks. The counter is
per bot identity and per room; any human message resets it.
Requires:
/add-slack), current enough to ship the
bot-inbound guard (src/channels/slack-a2a-guard.ts with the
setBotInboundPolicy seam). The default drop arrives with that payload;
this skill only adds the allowlisted-room admission on top.SLACK_INSTANCES — see the slack-multi-instance skill
for the env-key format. scripts/open-a2a-room.ts reads tokens by that
convention (SLACK_BOT_TOKEN_<NAME>; default → SLACK_BOT_TOKEN).mpim:write (open the room), mpim:history + mpim:read (see it), and
the message.mpim bot event (hear it). Apps provisioned through the
managed flow (apps.manifest.create + apps.managedInstall) already
include all of these.The policy module copied next imports setBotInboundPolicy from the installed
src/channels/slack-a2a-guard.ts. On a Slack payload that predates the guard,
that import takes down the channel barrel — and with it every adapter — so
verify the seam first. If the check fails, stop: re-run /add-slack from a
channels branch that ships the guard, then re-apply this skill.
grep -sq 'export function setBotInboundPolicy' src/channels/slack-a2a-guard.ts || { echo 'slack-a2a-rooms: src/channels/slack-a2a-guard.ts is missing or does not export setBotInboundPolicy. Installing anyway would break the channel barrel and take down every channel adapter. Update the installed Slack channel first (re-run /add-slack from a channels branch that ships the bot-inbound guard), then re-apply this skill.' >&2; exit 1; }
This skill ships three files alongside this document; copy them into the tree at the same relative paths (overwrite; the skill's copies are canonical):
src/channels/slack-a2a.ts
src/channels/slack-a2a.test.ts
scripts/open-a2a-room.ts
slack-a2a.ts — the admission policy: SLACK_A2A_ROOMS /
SLACK_A2A_MAX_HOPS parsing (re-read with a ~30s cache so a freshly opened
room needs no restart), the per-room, per-identity consecutive-hop counter
with human reset, and the slack:bot:<bot_id> re-attribution. The module
registers itself onto the guard's admission seam on import.slack-a2a.test.ts — the guard: drives the real channel barrel and the
real bot-inbound guard end-to-end (see step 4 for what it pins).open-a2a-room.ts — operator CLI to open a room (see Configuration).Append the self-registration import to the channel barrel (skipped if the line is already present). Appending at the end keeps it after the Slack channel's own imports:
import './slack-a2a.js';
Build first — it guards the typed setBotInboundPolicy call against guard
drift. The test imports the real channel barrel (a deleted or broken barrel
line goes red) and asserts the policy end-to-end: allowlisted-room admission
with slack:bot:<bot_id> re-attribution, non-listed-room drop, the hop limit
with human reset, per-room/per-identity budgets, and that a downstream throw
consumes no hop budget:
pnpm run build
pnpm exec vitest run src/channels/slack-a2a.test.ts
.env keys (both re-read with a ~30s cache — no restart needed after edits):
SLACK_A2A_ROOMS — comma-separated raw Slack channel ids (the MPIM ids the
opener script prints, e.g. G0AAAAAAA — note MPIM ids may start with G
or C depending on workspace vintage). Only these rooms admit bot-authored
inbound.SLACK_A2A_MAX_HOPS — consecutive bot-authored inbound messages allowed in
an A2A room without a human message before further bot messages are dropped.
Default 6.pnpm exec tsx scripts/open-a2a-room.ts --instances dana,eli --user U0AAAAAAA
The first listed instance opens the conversation (via conversations.open
with the human + the other bots' user ids, resolved through auth.test per
token) and posts an intro message. The script prints the channel id and
appends it to SLACK_A2A_ROOMS in .env. Without --user you get a
bots-only room, which needs at least three instances (Slack collapses a
two-party open into a 1:1 IM).
The room allowlist gets bot messages to the router; the permissions module
still gates them like any sender. Bot senders arrive as user id
slack:bot:<bot_id>, which starts unknown. After the first human mention in
the room auto-creates its messaging group, either set the room public:
pnpm exec tsx scripts/q.ts data/v2.db "UPDATE messaging_groups SET unknown_sender_policy='public' WHERE platform_id='slack:<channel id>'"
or keep request_approval and approve each slack:bot:<bot_id> sender once
(or add them as members of the agent group). A private A2A room with known
humans is a reasonable place for public.
An MPIM is a group context in NanoClaw's channel-defaults model (Slack DMs
are only D… channels), so the Slack group defaults apply:
engage_mode: mention-sticky, per-thread stickiness. That means a bot
replies when it is @-mentioned (then stays engaged in that thread) — it does
not answer every room message. Bot-to-bot conversation is therefore
mention-driven by design: bot A's reply reaches bot B's agent when it
@-mentions bot B, and the chain continues only as long as each reply
mentions the next speaker. Slack does not emit app_mention for
bot-authored messages, but mention detection still works: the Chat SDK's
text-level detector matches the bot's own <@U…> token, which the adapter
deliberately leaves unresolved in the text. This is the intended loop
governor alongside the hop limit — prompt the agents (group CLAUDE.md /
personality) to @-mention the sibling they want an answer from, and to stop
mentioning anyone when the exchange has converged.
src/channels/slack-a2a.ts,
src/channels/slack-a2a.test.ts, scripts/open-a2a-room.ts.import './slack-a2a.js'; line from src/channels/index.ts.SLACK_A2A_ROOMS and SLACK_A2A_MAX_HOPS from .env.unknown_sender_policy='public', and archive/leave the MPIMs from Slack
(the rooms themselves are ordinary Slack conversations; NanoClaw holds no
other state for them beyond the usual messaging-group/session rows).pnpm run build).With the skill removed, the channel guard's default applies everywhere again: bot-authored inbound is dropped in every room.
SLACK_A2A_ROOMS at the
bridge — before the router and before any sender-approval flow — with or
without this skill applied. If an install relies on a bot sender (another
workspace app posting into a channel the agent watches), add that room to
SLACK_A2A_ROOMS. Human messages are never affected.slack:bot:<bot_id> senders through the gate as an operator step (public
policy or per-bot approval). Should open-a2a-room.ts instead pre-create
the messaging group + wirings + members via ncl so a room works with zero
extra steps? That needs the host running and a choice of agent group per
bot — deferred.isMe
dropped upstream), so with two bots the effective conversation length is
roughly 2×maxHops messages; with k bots each message increments k−1
counters. If per-room total (not per-identity) semantics are wanted, the
counter needs to live host-side (router/session), not in the channel layer..env manually.message_changed / edited bot messages are not admitted (the adapter
only forwards unfurl data for edits) — fine for v1.users row for slack:bot:<bot_id> takes
whatever display name the bridge serialized (often unknown for bot
events, since event.username is frequently absent and event.user is
unset). A nicety would be resolving the bot's profile name via
bots.info — deferred.G,
newer ones with C. The allowlist matches exact ids so both work, but the
adapter's getChannelVisibility calls C… ids "workspace"-visible —
cosmetic only, nothing here branches on it.