☰
群聊遥控Claude Code:把队友电脑变成AI编码助手
2026/10/26 21:23:36 网站建设 项目流程

国庆七天,别人都在朋友圈晒人山人海,我窝在沙发上写了个奇怪的东西:一个群聊机器人。你只要在群里@一下队友的名字,再丢一句“帮我看下这个接口为啥超时”,任务就直接跑到他电脑上的Claude Code里执行,跑完之后结果自动回传到群里。相当于把每个队友的电脑,都变成了一个可以远程调用的AI编码助手。

先解释一下Claude Code。它是Anthropic推出的终端AI编程代理,装好之后你在项目目录里输入claude,它就能读代码、改文件、执行命令,像是一个长在终端里的AI工程师。但它默认是“一人一机一终端”的工作方式,你想让队友机器上的Claude Code帮你干活,要么远程登录那台机器,要么打电话让他把输出复制给你。我做这个群聊工具,就是想把“终端里的AI”拉到群聊这个日常协作界面里,让一条@消息成为一个远程调用指令。

玩了两天,效果比我预期的好:不装额外App,队友不用学习新操作,直接在群里喊话就能看到结果。整个过程我拆成了网关、Runner、Claude Code三层,每层都不复杂,但连起来之后还挺有“分布式AI执行集群”的感觉。下面我把架构思路、核心拆解、完整搭建过程以及踩过的坑都记录下来,尽量做到每一步都能照着抄。

1. 项目整体设计:为什么要在群聊里@队友电脑

1.1 这个项目到底做什么

最朴素的理解,就是给Claude Code包了一个“群聊入口”。Claude Code本身是一个交互式的命令行工具,在一个项目目录里运行后,它会分析这个项目的上下文,然后按照指令帮你改代码、跑测试、解释报错。在没有这个群聊工具之前,你想让队友电脑上的Claude Code去查一个问题,必须得先拿到那台机器的访问权限,然后自己开终端、切目录、执行claude。如果队友不在工位上,整个过程就卡住了。

现在链路变成了这样:

  1. 群机器人收到一条消息,判断有没有@某个人;
  2. 消息网关把文本解析成标准任务JSON,包含目标人、prompt、工作目录;
  3. 任务通过WebSocket推送到目标电脑上常驻的Agent Runner进程;
  4. Agent Runner收到任务,在指定项目目录里启动claude -p非交互模式执行;
  5. 执行完成后,Runner把stdout、stderr、退出码封装成结果;
  6. 结果发回网关,网关再通过群机器人接口,以回复的方式发回群里。

整个过程,队友和发起人都不需要在电脑前守着。以前要远程拜托队友跑一条命令,平均流程是“打电话—开终端—粘贴命令—等结果—截图发我”,现在直接在群里发一句“@张三 帮我在webapp目录跑一下npm test”就够了。

这个工具的核心价值,是把“人肉远程协助”变成了“消息驱动的自动调度”,让每个队友的电脑成为一个可寻址的执行节点。团队日常交流本来就在群里,任务和结果也沉淀在同一个会话流里,既方便追溯,也不会额外增加工具的学习成本。

1.2 架构选型背后的思考

可能有人第一反应是:为什么不直接用SSH连到队友电脑上跑?如果你的队友电脑有公网IP或者在公司内网可达,那SSH确实可以。但多数场景下,队友的电脑在办公网或家庭NAT后面,没有固定公网地址,直接开SSH入站还需要开放防火墙、配置端口转发,操作门槛和安全隐患都很大。

我采用的方案是“反连”:每一台电脑上的Agent Runner主动向外连到网关。这样不需要给电脑开任何入站端口,只要它能访问到网关的WebSocket地址就行。这个思路跟内网穿透类似,但是自建的、可控的、只服务于我们自己的任务协议。网关维护一份“在线电脑表”,Runner启动时握手注册,之后保持心跳。任务下发走同一条链路,结果是实时返回,延迟很低。

为什么用群聊而不是自己写个网页或者App?团队协作的现实是:大家每天大量时间泡在群里,消息流里天然带着上下文。在群里直接@,等于把任务和讨论放在同一个界面,不需要再切工具。而且群聊本身有“公证人”效果:谁派的活、给谁干的、干得怎么样,群里都有记录,责任分明。哪怕工具以后不维护了,聊天记录也能还原当时的现场。

技术选型方面,我用了Node.js 18+,主要看中它的WebSocket生态、child_process支持和机器人SDK齐全。群机器人我选的是钉钉企业机器人,因为团队本来就在用钉钉。飞书、企微、Discord也都一样,原理都是收消息回调或者长连接模式,然后通过API把回复发到群里。代码里我把机器人适配层抽象了一下,换平台只要改一个文件。

1.3 技术栈与目录结构

整个项目分为网关(gateway)和Runner(runner)两个独立进程。网关负责所有“大脑”工作:接收群消息、解析指令、维护在线设备表、下发任务、回传结果。Runner负责“手脚”工作:连上网关、接收任务、执行Claude Code、把结果传回。

技术栈清单如下:

  • Node.js 18+(内置fetch和WebSocket客户端支持,省去一堆polyfill)
  • @anthropic-ai/claude-code(命令行工具本体)
  • ws(WebSocket服务端/客户端库)
  • 钉钉机器人SDK(收发群消息)
  • better-sqlite3(可选,记录任务流水,方便回放)
  • pm2(守护网关和Runner进程,防止终端退出就挂掉)

实际项目目录我最后整理成了这个样子:

claude-group-chat/ ├── gateway/ │ ├── index.js # 网关主进程:接收群消息、路由、调度 │ ├── bot/ │ │ ├── dingtalk.js # 钉钉机器人适配器 │ │ └── feishu.js # 飞书适配器(预留) │ ├── parser.js # 群消息解析:@识别、命令提取 │ ├── dispatcher.js # 在线设备注册表 + WebSocket调度 │ └── tasks.js # 任务状态管理 ├── runner/ │ ├── index.js # 每台电脑上跑的Agent Runner入口 │ ├── executor.js # 真正spawn Claude Code执行任务的地方 │ └── config.json # Runner标识、项目白名单、密钥配置 ├── shared/ │ ├── protocol.js # 任务JSON的序列化/校验 │ └── logger.js └── scripts/ ├── register-runner.sh └── install-claude.sh

这个结构是调试中不断踩坑后才拆出来的。最开始我把网关和Runner写在一个文件里,实现起来确实快,但一涉及到“怎么区分多个Runner”“消息往哪路由”“结果和任务对不上”就乱了。拆成网关和Runner之后,边界清晰了很多——网关不需要关心Claude Code怎么跑,Runner也不关心群消息怎么解析,各自只用一套协议交流。

2. 核心环节拆解:群聊消息如何变成电脑上的执行动作

2.1 监听群消息并解析@指令

群机器人收到群消息的方法因平台而异,但核心回调数据大同小异:有发送人、发送群ID、消息类型、消息内容,以及@列表。以钉钉企业机器人为例,回调事件里会带上senderNick、senderId、conversationId和text,如果消息里@了谁,atUsers字段会有结构化信息。

第一件要处理的事情是消息去重。钉钉和飞书在异常情况或者服务器重试时,可能把同一条消息推送多次。网关必须维护一个最近消息ID集合,处理过就跳过,否则一条命令会被重复执行,很尴尬。我简单地用msgId + 群ID做key,放进一个滚动缓存里,过期时间设10分钟。

解析的目标是识别“@目标人 + prompt文本”。我在群里要求大家都用固定格式:先@机器人,再@队友,最后跟指令。但实际用起来大家经常乱序,所以parser要尽量宽容。核心逻辑是用结构化atUsers,不要用正则去文本里抠。

function parseGroupMessage(raw) { const text = raw.text.trim(); const mentions = raw.atUsers || []; // 情况1:只@了机器人 + 一个队友 const humanMentions = mentions.filter((u) => u.id !== BOT_USER_ID); if (humanMentions.length > 0) { const first = humanMentions[0]; const cleaned = text .replace(/@[^\s@]+/g, ' ') .replace(/\u200b/g, '') .replace(/\s+/g, ' ') .trim(); return { type: 'command', target: normalizeName(first.name), prompt: cleaned, }; } // 情况2:只@了队友,但没@机器人(为了防止误触发,还是处理了) const m = text.match(/@([^\s@]+)\s+([\s\S]+)/); if (m) { return { type: 'command', target: normalizeName(m[1]), prompt: m[2].replace(/\u200b/g, '').trim(), }; } return null; }

这里面有个非常重要的经验:不要纯靠正则从富文本里抠@名字。钉钉企业机器人回传的atUsers是结构化字段,飞书的消息里@可能出现在content元素中,标签是"at",里面的user_id才是真正的目标。如果直接用正则去抠,你会把零宽字符、昵称后缀、甚至@后面的空格都当成名字的一部分,导致目标匹配不上。除了用结构化字段,我还在normalizeName里做了兼容:去掉零宽字符、转成小写、允许全半角相等。

prompt提取也要小心。群里常见的消息是“@张三 帮我看一下为什么登录接口报500”,用上面代码把@部分用空格替换后,prompt就是“帮我看一下为什么登录接口报500”,干净多了。如果是“@张三@李四 你们分别跑下模块测试”,则只取第一个人作为target,其他人放入cc列表,这个在后面多播功能里再说。

2.2 任务投递:怎样把指令安全送到队友电脑

网关解析出任务后,要决定发给谁、怎么发。Runner和网关之间我设计了一套极简协议,所有通信消息都是JSON,字段固定。任务下发长这样:

{ "type": "task", "taskId": "uuid-v4", "target": "zhangsan", "cwd": "/home/zhangsan/work/webapp", "prompt": "run npm test and explain the failure", "timeoutMs": 120000, "from": "lisi", "nonce": "安全随机数" }

Agent Runner启动后,先向网关发一条注册消息:

{ "type": "register", "name": "zhangsan", "secret": "预共享密钥", "runnerId": "zhangsan-mbp" }

网关拿到注册消息后,校验密钥是否正确,如果正确就把Runner的WebSocket连接记录到在线表里。在线表是一个Map<personName, runnerInfo[]>,因为同一个人可能有多台电脑(比如工作机和家里的机器),我用runnerId区分。心跳机制是:网关每30秒向Runner发一个ping,Runner回pong,超过2分钟没收到心跳,就把这个Runner标记为离线。

为什么用WebSocket而不是HTTP轮询?因为Claude Code执行任务可能从几秒到几分钟不等,如果是传统HTTP请求,要么长连接占用,要么需要客户端频繁查询结果,很啰嗦。WebSocket天然是全双工,网关下发任务、Runner回ACK、Runner回结果、网关回确认,这些都可以在同一条连接里并行。消息的时序也用taskId来对齐,不会乱。

安全设计上,我做了三件事:

  • 预共享密钥校验:Runner和网关握手时校验密钥,防止陌生设备随便接入;
  • 任务nonce防重放:每条任务都带一次性的nonce,Runner处理完记录已消费的nonce,防止同一个任务报文被恶意重放多次;
  • 群消息来源校验:网关只处理来自“白名单群”的消息,非白名单群直接忽略。群里也不是谁的话都听,只有白名单用户可以触发调度,其他人发指令会被礼貌拒绝。

2.3 Claude Code执行与结果回传

这是整个链路里最有“硬核”感的一步。Runner收到任务后,并不需要自己理解prompt,它只需要在正确的目录里启动Claude Code,把prompt传给它,然后收集输出。Claude Code支持一种非交互的print模式,用-p参数直接给prompt,执行完自动退出,非常适合程序化调用。

我的executor.js核心逻辑如下:

const { spawn } = require('child_process'); function runClaude({ cwd, prompt, timeoutMs }) { return new Promise((resolve) => { const args = ['-p', prompt]; // 如果需要自动同意权限,可以加 --dangerously-skip-permissions // 但是我很不建议在队友电脑上用这个参数,除非是自己的机器且已知风险 const proc = spawn('claude', args, { cwd, env: { ...process.env, ...extraEnvFromConfig() }, shell: false, }); let stdout = ''; let stderr = ''; const timer = setTimeout(() => { proc.kill('SIGKILL'); resolve({ ok: false, error: 'timeout', stdout, stderr }); }, timeoutMs || 120000); proc.stdout.on('data', (d) => { stdout += d.toString(); }); proc.stderr.on('data', (d) => { stderr += d.toString(); }); proc.on('close', (code) => { clearTimeout(timer); resolve({ ok: code === 0, code, stdout, stderr }); }); proc.on('error', (err) => { clearTimeout(timer); resolve({ ok: false, error: err.message, stdout, stderr }); }); }); }

这里面有几个细节需要注意:

第一,shell:false一定要设。虽然我们传给Claude Code的是prompt而不是直接的shell命令,但Claude Code自己会用工具执行各种操作,如果父进程是以shell方式启动,会多一层不可控的解析。保持shell:false,spawn的claude可执行文件路径将由系统PATH解析,更安全。

第二,Claude Code第一次在某个项目目录运行时,会初始化一些配置,比如询问是否允许工具执行。print模式下遇到交互式提问会卡住。解决方法是让Runner在配置好的项目目录里预先执行一次claude,把该问的问题都答掉,或者通过环境变量/配置文件自动接受权限。但是这个“自动接受”也意味着Claude Code可以自行改文件、跑命令,所以我用allowedProjects白名单来限制Runner只能在这些项目目录里干活。

第三,执行结果回传的结构要清晰:

{ "type": "task_result", "taskId": "uuid-v4", "target": "zhangsan", "runnerId": "zhangsan-mbp", "ok": true, "code": 0, "stdout": "...", "stderr": "", "durationMs": 35420 }

网关收到结果后,会查看任务记录里的conversationId,然后调用群机器人接口,把stdout内容作为回复发到群里。如果执行失败,把stderr也带上。这样发起人和围观群众都能看到完整结果。

3. 实操记录:从零搭出这个群聊机器人

3.1 环境准备与Claude Code安装

先说Node.js环境。我建议用Node.js 18或20版本,因为很多WebSocket库和SDK对stream、buffer的处理在老旧版本上会有兼容问题。装好Node.js之后,全局安装Claude Code:

npm install -g @anthropic-ai/claude-code claude --version

如果后续想升级Claude Code,直接执行claude update。如果你发现claude命令找不到,多半是npm全局路径没加到PATH,用npm prefix -g查看全局路径,然后把它加到shell配置文件里。

对于想接入其他模型的情况,Claude Code可以在环境变量里指定兼容的API服务。比如团队里有人用DeepSeek这类提供了Anthropic兼容接口的模型平台,可以这么配置:

export ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic" export ANTHROPIC_AUTH_TOKEN="sk-xxxx" export ANTHROPIC_MODEL="deepseek-chat"

这样claude -p就会走你配置的模型服务,而不需要改代码。但注意一点:这些环境变量不要写进用户全局配置,否则你日常的Claude Code也会被带偏,影响别的项目。建议放在Runner进程的.env或者环境变量文件里,只对Runner生效。

在Windows上,Claude Code偶尔会有PTY相关的兼容问题,所以我建议在WSL 2里安装和执行,跑起来更稳。macOS和Linux基本没问题,唯一要注意的是项目路径里尽量别有空格,否则后面spawn会徒增很多麻烦。

3.2 编写消息网关服务

网关是整套系统的“邮局”。我用的是钉钉企业机器人的Stream模式,因为它不需要公网回调地址,可以在本地直连服务,省去了一台带公网IP的服务器,也避免了各种网络配置的折腾。如果你用飞书,飞书开放平台的“长连接”模式也有类似效果。

网关主进程需要做四件事:

  1. 启动WebSocket服务,等待Runner接入;
  2. 接入钉钉机器人回调,收到群消息后丢给parser;
  3. 解析成功就查找在线Runner,下发任务;
  4. 收到Runner结果后,通过钉钉机器人API回复群聊。

核心骨架如下:

const WebSocket = require('ws'); const { v4: uuidv4 } = require('uuid'); const { handleGroupCommand } = require('./router'); const wss = new WebSocket.Server({ port: 8123 }); const online = new Map(); // personName -> [{ runnerId, ws, lastSeen }] wss.on('connection', (ws, req) => { ws.on('message', (buf) => { const msg = JSON.parse(buf.toString()); if (msg.type === 'register') { if (msg.secret !== process.env.RUNNER_SECRET) { ws.send(JSON.stringify({ type: 'error', error: 'bad secret' })); ws.close(); return; } const info = { runnerId: `${msg.name}-${msg.hostname || 'unknown'}`, ws, lastSeen: Date.now(), }; const list = online.get(msg.name) || []; list.push(info); online.set(msg.name, list); ws.send(JSON.stringify({ type: 'registered', runnerId: info.runnerId })); } else if (msg.type === 'task_result') { handleTaskResult(msg); } else if (msg.type === 'pong') { // 更新lastSeen,略 } }); }); // 从钉钉Stream回调收到群消息 function onGroupMessage(rawEvent) { const parsed = parser.parseGroupMessage(rawEvent); if (parsed) { handleGroupCommand(parsed, rawEvent); } }

handleGroupCommand查在线表,如果目标人离线,立刻回群里“张三的电脑不在线”。如果在线但有多台,选lastSeen最新的那台,然后生成task,通过对应的WebSocket连接下发。同时,把taskId、群ID、发起人存进一个Map,方便结果回来时回消息。

这里有个值得做的小优化:任务下发后,马上在群里回一条“已派给张三,任务ID:xxx,请稍等”,然后等结果回来再回一条带完整输出的消息。不要等到Claude Code跑完才回一条,那样中间这段等待时间群里的人会以为机器人死掉了。尤其是长任务,中间回一条状态消息能极大缓解焦虑。

3.3 编写电脑端Agent Runner

Runner是跑在队友电脑上的常驻进程,启动后连网关,然后就是一个循环:收任务、执行、回结果。为了稳定,我用pm2守护,进程挂掉自动拉起,Runner代码里还做了断线重连。

Runner入口文件核心逻辑:

const WebSocket = require('ws'); const { runClaude } = require('./executor'); const config = require('./config.json'); let ws = connect(); function connect() { const ws = new WebSocket(config.gatewayUrl); ws.on('open', () => { ws.send(JSON.stringify({ type: 'register', name: config.name, runnerId: config.runnerId, secret: config.secret, hostname: require('os').hostname(), })); }); ws.on('message', async (buf) => { const msg = JSON.parse(buf.toString()); if (msg.type === 'ping') { ws.send(JSON.stringify({ type: 'pong' })); return; } if (msg.type === 'task') { ws.send(JSON.stringify({ type: 'task_ack', taskId: msg.taskId })); const result = await runClaude({ cwd: resolveCwd(msg.cwd), prompt: msg.prompt, timeoutMs: msg.timeoutMs || 120000, }); ws.send(JSON.stringify({ type: 'task_result', taskId: msg.taskId, target: config.name, runnerId: config.runnerId, ...result, })); } }); ws.on('close', () => { setTimeout(() => { ws = connect(); }, 3000); }); return ws; }

config.json放在Runner目录里,内容里最关键的是白名单和密钥:

{ "name": "zhangsan", "runnerId": "zhangsan-mbp", "secret": "替换成自己的密钥", "gatewayUrl": "ws://your-gateway-host:8123", "allowedProjects": [ "/home/zhangsan/work/webapp", "/home/zhangsan/work/tools" ], "defaultCwd": "/home/zhangsan/work/webapp" }

resolveCwd这个函数就是实现白名单校验:

function resolveCwd(cwd) { if (!cwd) return config.defaultCwd; if (config.allowedProjects.includes(cwd)) return cwd; throw new Error(`项目目录不在白名单: ${cwd}`); }

如果网关下发的cwd不在白名单,Runner会直接返回一个错误结果,而不是去启动Claude Code。这就防止了某个人在群里乱指定路径,让队友电脑去跑一个未知目录的命令。

Claude Code的模型配置放在Runner的.env里是最合理的。这样以后换模型,只需要改Runner的环境变量,不影响网关和群机器人。我实际用的.env长这样:

ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic ANTHROPIC_AUTH_TOKEN=sk-xxxx ANTHROPIC_MODEL=deepseek-chat API_TIMEOUT_MS=120000

3.4 联调排错与效果

搭建过程中,调试顺序特别重要。我一开始就在群里发消息,结果根本不知道是解析问题还是Runner没跑起来,排查效率很低。后来我改成一层一层验证:

第一步,先不接群机器人,在本地起一个WebSocket服务,写一个模拟Runner,验证注册、下发、回传这条基本链路。用wscat或者一个Node脚本直接连上来发消息,确认协议没问题。

第二步,把Runner接到真实Claude Code上,本机直接spawn执行,打印输出看是否正常。很多问题在这一步就能暴露:比如环境变量没传、模型鉴权失败、项目目录不对等。

第三步,把网关和群机器人接好,在群里发一条“@机器人 @张三 查看当前目录的git log,并总结最近5次提交”,然后观察网关日志。注意日志要打印完整的parser输出,包括解析出来的target和prompt,这样一眼就能看出是因为@没解析出来,还是因为跑到了别的目录。

第四步,让队友从自己账号在群里发一条消息,验证不同账号、不同@方式(比如只@队友不@机器人)能否正确识别。

整个联调里最值得说的问题是:Claude Code在print模式下,如果项目目录是第一次执行,可能会弹出交互式的权限确认,导致进程挂起。我后来用一个笨办法解决:人工在Runner所在机器上,让每个白名单项目都预先跑过一次claude,把初始化状态确认好,然后之后再用-p模式就顺畅多了。如果你不想手动初始化,也可以在prompt里明确写“对项目文件执行只读操作”等限制,配合Runner的最小权限用户,降低交互式确认的风险。

实际跑通的场景是这样的:

张三在群里发:@机器人 @李四 帮我跑一下 webapp 的 build,报错了贴给我 网关解析:target=李四, prompt="帮我跑一下 webapp 的 build,报错了贴给我" 网关查在线表:李四的 runner 在线,下发任务 李四电脑上的 Runner 启动 claude -p,Claude Code 分析项目、执行 build 结果回到群里,附带 stdout:
李四的电脑 回复 @张三: Build finished with warnings: - chunk size exceeds 500KB - asset size limit was not set 任务耗时 47s

整个过程李四午休不在电脑前,他的电脑自己就把活干了。这种体验第一次跑通的时候,确实很有成就感。

4. 常见问题与排查技巧实录

4.1 群聊消息@解析不到队友名字

这是最容易踩的第一个坑。我在钉钉自定义机器人上调试的时候,纯靠正则去匹配文本里的@,结果总是匹配到一堆\u200b零宽字符和昵称后缀。后来果断改成优先使用SDK里结构化的atUsers,不再依赖文本抠字。

如果你用的平台回传消息里没有结构化字段,只能靠正则,那么有一个补救措施:清字符。在解析前先把字符串里的所有零宽字符去掉,再匹配:

const cleanText = text.replace(/[\u200b-\u200d\uFEFF]/g, '').trim();

同时,normalizeName要做归一化,比如把中文姓名转成小写、去掉中间空格。网关在线注册表里可以存多个别名,比如“张三”和“zhangsan”都映射到同一个runner。这样大家@的时候不管用中文还是英文名,都能对上。

4.2 多台电脑同时在线,消息路由错乱

同一人名下挂了两台电脑后,任务到底发给谁?我一开始简单地把Map做成personName -> websocket,后来第二台电脑接入时把第一台覆盖掉了,结果任务永远发到新机器上。后来改成personName -> websocket[],任务下发时选择lastSeen最新的一台,也就是最近活跃的设备。如果那台设备不在线,再回退到其他设备。

还有个辅助手段:给每台Runner加一个hostname,在群里回复结果时带上,例如“李四的电脑(mac-mini)回复:xxx”。这样消息一多,大家也清楚是哪台机器干的活。如果你希望的是广播,比如“@李四 的电脑都跑一下测试”,那就在任务协议里加一个broadcast: true,网关遍历这个人的所有在线Runner分别下发。

4.3 Claude Code执行超时或输出太多

Claude Code的print输出非常啰嗦,它会把工具调用、思考过程、内部日志全部打到stdout,直接回群里就是灾难。我的处理方式是三重过滤:

  1. 在prompt里给约束:“请只输出最终结论,不要列出工具调用步骤。”
  2. 在解析stdout时,去掉终端颜色码(\x1b[...m)以及Claude Code自己的日志行(比如以[Claude]开头的行)。
  3. 限制输出长度。我设了一个上限比如20000字符,超过6400就截断前半部分保留最后6400,因为结论通常在最后。

另一个问题是超时。Claude Code在-p模式下虽然非交互,但某些任务(比如让AI自己分析一个大型代码库)会跑很久。我设置了默认120秒超时,超了就SIGKILL杀掉进程,把已经收集到的stdout回传。这样不会出现任务卡死、群聊永远没反应的情况。如果你希望等待长任务,就调大timeoutMs,但最好在群里先说明“预计需要几分钟”。

4.4 安全边界与权限控制

这是整个项目里最不能偷懒的部分。虽然这个工具本质上是给信任的队友用,但一旦接入群聊,攻击面就变成“任何一个能发言的人”。我做了下面几层控制:

第一层,群聊入口白名单。网关只会处理白名单群的消息,非白名单群全部忽略。处理消息时也要校验发送人的身份,不在白名单用户列表里的人,直接回复“您没有权限使用我”。

第二层,Runner执行白名单。Runner只能在一个或者几个指定的项目目录里执行任务。如果任务里的cwd不在allowedProjects中,Runner直接拒绝执行并返回错误。这样即使有人在群里发一条“帮我格式化硬盘”,Runner也会先拒绝,而不是傻傻地交给Claude Code。

第三层,危险命令拦截。我在Runner里加了一个简单的关键词扫描,如果prompt里出现rm -rf、mkfs、curl | sh、sudo、shutdown等危险词,就要求发起人在群里再发一次“确认执行”才能继续。这个拦截并不完美,但能防住90%的误操作。

第四层,以最小权限运行。Runner进程绝对不要用root或者超级管理员用户运行。我在团队电脑上新建了一个低权限用户,只给它读写业务项目目录的权限。即使Claude Code被恶意prompt引导去执行危险命令,它能造成的破坏也极其有限。

另外,网关到Runner的WebSocket建议用wss://而不是ws://,尤其是跨公网传输时。如果你的网关部署在云端,TLS基本是必选项;如果只在公司内网联调,可以先用ws凑合,但密钥还是要保证随机且长。

5. 后续还能怎么玩

做完这个“群聊遥控器”之后,我觉得最值得扩展的方向是把它和团队自动化流程绑定。现在的Runner能接收群里的一条命令去跑Claude Code,本质上是一个“人工触发的分布式执行器”。在此基础上,可以加入定时任务,比如每天早上九点,让网关主动往每台电脑的Runner发任务,执行代码规范检查、跑一遍单测,然后把结果汇总到群里。这样相当于把一台台电脑变成了团队自己的轻量CI节点,但执行的是“AI能理解的指令”,而不是固定的CI脚本。

另一个方向是把群聊上下文喂给Claude Code。大家经常在群里讨论一个问题,说到一半才想起来“该让AI去看代码了”。这个工具可以改进成:群里的讨论摘要自动生成prompt,然后@机器人去某台电脑上做分析。比如大家吵了一下午“为什么userCenter模块接口变慢了”,机器人可以直接基于讨论内容,让队友电脑上的Claude Code去看接口日志和代码实现,给出一个技术结论。

还有一些小优化也很实用:Runner可以支持“流式进度回传”,Claude Code每打印一段输出,就实时发到群里,像你在终端里看它干活一样;网关可以增加任务审批开关,高危操作必须有两个人在群里确认才执行;Runner可以把标准输出加上一个签名(比如hash),确保回传结果没有被中间人篡改。

我个人的体会是,这个工具真正的门槛不在技术,而在于“流程信任”。当大家发现你在群里@一下他的电脑,他的机器就自动干活时,第一反应往往是“这东西安不安全”“会不会乱动我的环境”。所以我在把工具推广给队友之前,提前录了一段短视频:从在群里发消息,到电脑上出现Claude Code进程,再到结果回传到群里,全流程展示了一遍。队友亲眼看了一遍流程后,才放心把自己的电脑挂上来。

如果你也想在团队里做类似的工具,强烈建议先拿自己的电脑跑通整个流程,再让队友用只读项目试水。平时设置里也尽量保持“危险命令二次确认”的状态,虽然多一步操作,但在这种跨设备执行场景里,多一步确认永远比出了事故再修复要划算。国庆这几天假,我没有出去玩,但看着群里一条条任务在队友电脑上自动跑完、自动回传,确实感受到了一种和写单机脚本完全不同的乐趣——AI代理想法变成日常协作的一部分,大概就是这种体验吧。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询