版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | http://vearne.cc

1. 引言

随着本地AI编程助手如Codex、Claude Code和Cursor的普及,开发者开始习惯让AI长时间在后台执行复杂任务:读取文件、运行测试、修改代码、等待权限批准。在这些场景中,你不得不反复切回终端或编辑器确认进度——是否有更直观的方式了解Agent的状态?

你可能已经在朋友圈中看到,有人用类似信号灯的硬件,实时展示AI编程助手的状态。

AI编程助手的状态是如何采集并展示的呢?

最近萌叔发现了一个纯软件实现的vibe coding信号灯 vearne/vibecoding-signal-light,通过悬浮三色灯、菜单栏图标和Touch Bar实时展示AI Agent的工作状态。 本文将深入剖析其技术实现,特别是如何从Cursor编辑器获取状态并映射为灯语。

2. 整体架构:双Swift进程协作

项目采用了清晰的双进程设计,全部基于Swift构建:

  • Swift CLIsignal-light-cli):无状态命令行工具,由Cursor/Codex/Claude Code的hook调用。负责解析事件、更新状态文件、发送跨进程通知。
  • Swift Appsignal-light-mac):macOS AppKit长驻应用。读取状态文件,通过动画定时器驱动三色灯UI渲染。

CLI写完状态后,并非只靠App轮询文件。它会双通道通知App立刻刷新:

  1. Darwin CFNotificationcom.vibecoding.signal-light.status-changed)——跨进程、低延迟
  2. DistributedNotificationCenter——同用户会话内的兜底通知

App侧同时监听这两种通知,并用文件系统监控作为最终兜底,因此灯语能接近实时变化。

2.1 配置Cursor Hook

应用安装时,会创建 ~/.cursor/hooks.json

Cursor 的 hooks.json 是一套事件驱动的中间件机制,用来在 Cursor Agent(AI 助手)特定生命周期节点插入自定义脚本,从而观察、拦截、控制或扩展 AI 的行为。可以把它理解为挂在 AI 工作流流水线上的“钩子”。

内容大致如下:

{
	"hooks": {
		"afterAgentResponse": [{
			"command": "\/usr\/local\/bin\/cursor-signal-hook",
			"timeout": 5,
			"type": "command"
		}],
		"afterAgentThought": [{
			"command": "\/usr\/local\/bin\/cursor-signal-hook",
			"timeout": 5,
			"type": "command"
		}],
		"afterFileEdit": [{
			"command": "\/usr\/local\/bin\/cursor-signal-hook",
			"timeout": 5,
			"type": "command"
		}]
		...
	},
	"version": 1
}

Cursor 侧注册了约 20+ 个 hook 事件(完整列表见附录);高频几个包括 beforeSubmitPromptpreToolUse / postToolUseafterFileEditpermissionRequeststop 等。

/usr/local/bin/cursor-signal-hook 指向 signal-light-cli

注意:由于 signal-light-cli 是被 Cursor Hook 触发执行的,所以存在多个 signal-light-cli 并发执行的可能。(vibecoding-signal-light 使用了文件锁解决互斥问题)

2.2 事件到内部信号的映射

Hook 触发后,CLI 会把原始事件名规范化,再查表映射为内部 signal。Cursor 侧的几条典型链路如下:

Cursor Hook 事件内部 signal灯语含义(默认)
beforeSubmitPromptthinking绿灯慢闪:开始思考
preToolUse / beforeShellExecution / beforeReadFileworking绿灯慢闪:正在干活
postToolUse / afterFileEdit / afterAgentResponsetool_done绿灯慢闪:工具告一段落,Agent 仍活跃
permissionRequestpermission红灯闪烁:需要你授权
postToolUseFailureblocked红灯闪烁:失败/卡住
stopdone绿灯常亮:本轮结束,回到空闲

Codex、Claude Code 也有各自的事件表,但最终都收敛到同一套内部 signal(如 thinkingworkingpermissiondone),App 只消费这套统一状态,不关心事件来自哪个 Agent。

2.3 状态信息

/private/tmp/signal-light/sessions.json 按 session 记录各路 Agent 的最新信号,形如:

{
  "sessions" : {
    "27772c50-e0de-4fc7-a025-d5df4f04ac0d" : {
      "model" : "default",
      "signal" : "done",
      "source" : {
        "bundle_identifier" : "com.todesktop.230313mzl4w4u92",
        "captured_at" : 1784008749.993353,
        "localized_name" : "Cursor",
        "process_identifier" : 48613
      },
      "updated_at" : 1784008749.9936671
    },
    "93b39e2b-b2ad-4824-b1a9-14b89b687c42" : {
      "model" : "default",
      "signal" : "session_start",
      "source" : {
        "bundle_identifier" : "com.todesktop.230313mzl4w4u92",
        "captured_at" : 1784008238.4849172,
        "localized_name" : "Cursor",
        "process_identifier" : 48613
      },
      "updated_at" : 1784008238.485254
    }
  }
}

/private/tmp/signal-light/current_status.json 是聚合后的当前状态:

{
  "aggregate" : "idle",
  "updated_at" : 1784008749.994518
}

实际上影响信号灯显示的主要是这个文件。多会话并存时,会取 updated_at 最新的那个有效 session 作为 aggregate,避免旧会话把灯“卡住”在过时告警上。

2.4 规则判断与信号灯显示

各 Agent(Codex、Claude Code、Cursor)的 hook 事件 → 映射成内部状态 → 再按规则显示颜色/闪烁。

可以在 App 界面上配置内部状态 → 显示样式的映射规则

3. 总结

整条链路可以压成一句话:

Hook 产出事件 → CLI(带文件锁)写成 session/聚合状态 → Darwin/Distributed 通知唤醒 App → App 按可配置规则渲染灯语。

纯软件信号灯并不神秘:关键是把各家 Agent 生命周期事件收敛成同一套内部 signal,再用跨进程通知驱动常驻 UI。映射规则可自定义,也因此容易扩展到更多本地 AI 编程助手。

附录:Cursor Hook 事件一览

安装时写入 hooks.json 的主要 Cursor 事件包括:

事件典型用途
sessionStart / sessionEnd会话起止
beforeSubmitPrompt用户提交提示
preToolUse / postToolUse / postToolUseFailure工具调用前后
subagentStart / subagentStop子 Agent
beforeShellExecution / afterShellExecutionShell 执行
beforeMCPExecution / afterMCPExecutionMCP 调用
beforeReadFile / afterFileEdit读文件 / 改文件
preCompact上下文压缩
afterAgentResponse / afterAgentThoughtAgent 回复与思考
beforeTabFileRead / afterTabFileEditTab 相关读写
permissionRequest权限请求
stop停止/结束一轮
workspaceOpen工作区打开

以上事件最终都会经 CLI 映射表落到 thinking / working / tool_done / permission / blocked / done 等内部 signal,再驱动信号灯显示。


微信公众号