用Claude Code钩子及时接回任务
Agent Arcade利用Claude Code的停止、权限请求和通知钩子,把状态写入本地文件,让终端游戏在需要用户时暂停。该方案无需服务端、套接字或后台守护进程,可复用于其他任务提醒场景。
AI 深度解读
等 Claude Code 或 Codex 干活时,盯着转圈浪费时间,切去别处又容易忘记回来。作者做了一个终端小游戏工具 Agent Arcade:任务结束或需要人工确认时,游戏立即暂停并提示你。
它有意思的地方,是把 AI 编程工具的事件变成其他程序能接收的信号。整套机制只用了 hook 和一个文件。对于想给日常 AI 编程加提醒的开发者,这个实现比游戏本身更值得拆开看。
一个文件,怎么让两个终端互相报信
Agent Arcade 的分工很简单:Claude Code 在特定事件发生时执行命令,另一个终端里的游戏负责检查结果。
任务结束执行 agent-arcade notify,需要用户处理权限请求时执行 agent-arcade notify ask。命令只做一件事:往 ~/.agent-arcade/signal 写入 done 或 ask。
下面是信号写入逻辑,不需要服务器、socket 或后台守护进程:
// bin/agent-arcade.mjs
else if (cmd === 'notify') {
fs.mkdirSync(DIR, { recursive: true });
fs.writeFileSync(SIGNAL, arg === 'ask' ? 'ask\n' : 'done\n'); // ~/.agent-arcade/signal
}
要看的是最后一行:文件只保存当前信号,没有维护事件队列。
游戏在每个 tick,也就是每次更新循环里,检查文件的修改时间:
// src/shell.mjs
const stamp = () => { try { return fs.statSync(SIGNAL).mtimeMs; } catch { return 0; } };
let seen = stamp();
function checkAgent() {
const t = stamp();
if (t === seen) return; // 没有新事件
seen = t;
let kind = 'done';
try { kind = fs.readFileSync(SIGNAL, 'utf8').trim() === 'ask' ? 'ask' : 'done'; } catch {}
agent = kind; // 显示横幅并暂停游戏
out.write('\x07'); // 终端响铃
}
这里最关键的是 let seen = stamp():启动时先记住已有时间,因此不会把启动前的旧通知当成新事件。后续比较 mtimeMs,则让连续两次内容相同的 done 也有机会触发两次提醒。\x07 用于终端响铃。
Codex 也能接入。作者使用它每轮结束后运行程序的 notify 设置,写入同一个信号文件。不过,原文没有给出 Codex 的具体配置。
提醒慢六秒,可能是选错了 hook
作者最初用 Notification 表示「Claude 需要你」。后来,一位 Hacker News 评论者指出:其中的权限提示通知,要在用户约六秒没有输入后才触发。
玩游戏时一直按键,恰好会让这个通知来晚。
作者随后改用 PermissionRequest,在权限提示出现时就写入 ask。这个 hook 只负责通知,不替用户批准或拒绝操作。
如果你要捕获权限请求,应该监听请求出现的事件,而不是等待空闲后的通知。
这条经验有明确的修复过程,但别把范围扩大:原文说明的是权限请求,没有证明它覆盖所有「agent 需要人」的情况。
收到信号后,游戏也不能粗暴地切换状态。作者补了几处交互处理:
- 用
\x1b[?1004h开启终端焦点事件,失焦时收到\x1b[O并暂停,回到终端时收到\x1b[I。 - 每次恢复都先做 3-2-1 倒计时,避免玩家刚回来就撞墙。
- 横幅出现后的最初一段时间忽略按键,防止原本用于操控游戏的输入误操作提示。
真正费工夫的是接入日常工作流
早期安装需要先执行 npm install -g agent-arcade,再执行 agent-arcade setup,后者会修改 ~/.claude/settings.json。
现在作者把它做成 Claude Code 插件:
/plugin install agent-arcade --marketplace Kuldeep-045/agent-arcade
安装后执行 /arcade。按作者描述,它是立即执行的命令,不需要模型再跑一轮,Claude 正在处理任务时也能打开游戏,而且不会往对话里添加内容。
插件监听 classic.Stop 和 classic.PermissionRequest,分别调用 notify 与 notify ask。代码给调用传入了 5000 参数,用 .catch(() => {}) 忽略失败,并通过 next(e) 继续原来的事件处理。这个设计让提醒功能尽量不干扰 Claude 的正常工作,但失败也可能悄无声息。
仓库额外提供插件 manifest 和 .claude-plugin/marketplace.json;没有全局安装时,插件回退到 npx。
打开游戏还要处理终端差异。/arcade 优先运行 tmux display-popup -E -w 70% -h 70% agent-arcade;其次尝试通过 AppleScript 打开 Ghostty 的 quick terminal 或分屏,再尝试 iTerm2 的垂直分屏。都不适用,就提示用户新开标签页运行 agent-arcade。
游戏本身反而简单:每个游戏提供 start、tick、key、draw、score,屏幕、输入、暂停、横幅和最高分交给公共外壳。作者说贪吃蛇约 60 行,前提正是这些通用能力已经抽出来了。
我的判断
我认可这个规模下的取舍。对于同一台机器上、低频发生的完成提醒,一个文件就能把两个进程接起来,容易理解,也方便排查。
但它保存的是最新状态,不保证逐条交付。如果两次写入发生在两次检查之间,前一个事件可能被覆盖;多个 agent 共用同一路径时,也没有会话标识可供区分。原文没有给出并发验证、提醒延迟或资源开销的对照数据。
因此,修改时间检测适合这个小工具,不能据此推导它比 socket 或事件队列更可靠。后两者需要更多实现工作,却更适合明确区分会话、保留连续事件的需求。
至于玩游戏是否比系统通知更能减少分心,作者没有提供对照数据。这是个人工作习惯驱动的产品尝试。真正可以复用的,是准确选择 hook,以及把提醒逻辑与 agent 的任务执行分开。
能拿来做什么
- 如果你经常等 Claude Code 完成任务,可以按原文命令安装插件,再用
/arcade体验暂停提醒是否适合自己的节奏。 - 给自己的 Node 终端工具加完成提醒时,可以借用「hook 写文件、界面检查修改时间」的做法;需要同时运行多个任务,就先补上会话区分。
- 实现权限提醒时,优先使用
PermissionRequest。提醒只展示状态,把批准和拒绝留在原来的权限界面。 - 扩展终端小游戏时,沿用
start、tick、key、draw、score接口,把暂停、恢复倒计时和输入保护放在公共外壳里。