import { z } from 'zod/v4' import type { ToolResultBlockParam } from 'src/Tool.js' import { buildTool } from 'src/Tool.js' import { lazySchema } from 'src/utils/lazySchema.js' const LIST_PEERS_TOOL_NAME = 'ListPeers' const inputSchema = lazySchema(() => z.strictObject({ include_self: z .boolean() .optional() .describe('Whether to include the current session in the list. Defaults to false.'), }), ) type InputSchema = ReturnType type ListPeersInput = z.infer type PeerInfo = { address: string name?: string cwd?: string pid?: number } type ListPeersOutput = { peers: PeerInfo[] } export const ListPeersTool = buildTool({ name: LIST_PEERS_TOOL_NAME, searchHint: 'list peers sessions discover uds socket messaging', maxResultSizeChars: 50_000, strict: true, get inputSchema(): InputSchema { return inputSchema() }, async description() { return 'Discover other Claude Code sessions for cross-session messaging' }, async prompt() { return `List active Claude Code sessions that can receive messages via SendMessage. Returns an array of peers with their addresses. Use these addresses as the \`to\` field in SendMessage: - \`"uds:/path/to.sock"\` — local sessions on the same machine (Unix Domain Socket) - \`"bridge:session_..."\` — remote sessions via Remote Control Use this tool to discover messaging targets before sending cross-session messages. Only running sessions with active messaging sockets are returned.` }, isConcurrencySafe() { return true }, isReadOnly() { return true }, userFacingName() { return LIST_PEERS_TOOL_NAME }, renderToolUseMessage() { return 'ListPeers' }, mapToolResultToToolResultBlockParam( content: ListPeersOutput, toolUseID: string, ): ToolResultBlockParam { const lines = content.peers.map( p => `${p.address}${p.name ? ` (${p.name})` : ''}${p.cwd ? ` @ ${p.cwd}` : ''}`, ) return { tool_use_id: toolUseID, type: 'tool_result', content: lines.length > 0 ? `Found ${lines.length} peer(s):\n${lines.join('\n')}` : 'No peers found.', } }, async call(_input: ListPeersInput, context) { // Peer discovery uses the concurrent sessions PID registry and // UDS socket directory. The implementation scans for live sockets // and optionally includes Remote Control bridge peers. const peers: PeerInfo[] = [] const seen = new Set() const addPeer = (peer: PeerInfo): void => { if (seen.has(peer.address)) return seen.add(peer.address) peers.push(peer) } /* eslint-disable @typescript-eslint/no-require-imports */ const udsMessaging = require('src/utils/udsMessaging.js') as typeof import('src/utils/udsMessaging.js') const udsClient = require('src/utils/udsClient.js') as typeof import('src/utils/udsClient.js') const bridgePeers = require('src/bridge/peerSessions.js') as typeof import('src/bridge/peerSessions.js') /* eslint-enable @typescript-eslint/no-require-imports */ const messagingSocketPath = udsMessaging.getUdsMessagingSocketPath() if (messagingSocketPath) { // Self entry for reference if (_input.include_self) { addPeer({ address: udsMessaging.formatUdsAddress(messagingSocketPath), name: 'self', pid: process.pid, }) } } for (const peer of await udsClient.listPeers()) { if (!peer.messagingSocketPath) continue addPeer({ address: udsMessaging.formatUdsAddress(peer.messagingSocketPath), name: peer.name ?? peer.kind, cwd: peer.cwd, pid: peer.pid, }) } for (const peer of await bridgePeers.listBridgePeers()) { addPeer(peer) } return { data: { peers }, } }, })