自定义跳转规则
Vibe Island 在刘海面板里显示每一个 AI 编程 agent 的 session,点击 session 就跳到运行它的终端。 对于内置支持的终端(iTerm2、Ghostty、Warp、VS Code、Cursor 等),跳转能精确落到对应的 tab、pane 或窗口;对于其他终端或 IDE,默认只能把 App 窗口带到前台。
跳转规则用来补上这一环:你的 App 注册一个 URL scheme,再在磁盘上放一个 JSON 文件,Vibe Island 就会带着它掌握的 session 线索打开你的 URL,由你的 App 把线索映射到自己的 tab、pane 或窗口。
需要 Vibe Island 1.0.22 或更新版本,不需要改变你的 App 运行 agent 的方式。
- Vibe Island 先把你的 App 带到前台。
- 等焦点稳定后,用填好线索的 URL 打开你的 App。
- 你的 App 收到 URL,切到匹配的 tab、pane 或窗口。
如果没有规则匹配该 App 的 bundle identifier,行为与以前完全一致:内置终端走原生跳转,其余只激活窗口。
第一步:注册 URL scheme handler
Section titled “第一步:注册 URL scheme handler”线索到目标的映射由你的 App 负责。Vibe Island 不需要知道你的 tab 树、pane id 或 socket 协议。
Electron
// main processapp.setAsDefaultProtocolClient('yourterm');
app.on('open-url', (event, url) => { const parsed = new URL(url); if (parsed.host !== 'focus') return;
routeFocusRequest({ sessionId: parsed.searchParams.get('session'), tty: parsed.searchParams.get('tty'), pid: Number(parsed.searchParams.get('pid') || 0) || null, cwd: parsed.searchParams.get('cwd'), tmuxPane: parsed.searchParams.get('tmuxPane'), });});原生 macOS(Swift)
// Info.plist: CFBundleURLTypes -> CFBundleURLSchemes = ["yourterm"]
struct TerminalFocusRequest { let sessionId: String? let tty: String? let pid: Int? let cwd: String? let tmuxPane: String?}
func application(_ app: NSApplication, open urls: [URL]) { guard let url = urls.first, let components = URLComponents(url: url, resolvingAgainstBaseURL: false), components.host == "focus" else { return }
let query = Dictionary(uniqueKeysWithValues: (components.queryItems ?? []).map { ($0.name, $0.value ?? "") })
TerminalFocusRouter.shared.focus(TerminalFocusRequest( sessionId: query["session"], tty: query["tty"], pid: query["pid"].flatMap(Int.init), cwd: query["cwd"], tmuxPane: query["tmuxPane"] ))}focus 只做一件事:把公开线索解析成你自己的目标,然后切过去。
第二步:放一个 JSON 文件
Section titled “第二步:放一个 JSON 文件”创建 ~/.vibe-island/integrations/<your-app-name>.json:
{ "displayName": "YourTerm", "bundleIdentifier": "com.example.yourterm", "jumpRule": { "method": "urlScheme", "template": "yourterm://focus?session={session_id}&tty={tty}&pid={pid}&cwd={cwd}&tmuxPane={tmux_pane}" }}Vibe Island 启动时加载这个目录。session 的 bundle identifier 匹配时,就用这条规则跳转。 显式规则的优先级高于内核识别:如果你的终端复用了 Ghostty、WezTerm 或 xterm.js 内核, 但自己管理 tab 和 pane,这条规则就是精确跳转的来源。
| 字段 | 必需 | 说明 |
|---|---|---|
displayName | 否 | 刘海面板里显示的名称 |
bundleIdentifier | 是 | 你的 App 的 macOS bundle identifier,用作匹配键 |
jumpRule.method | 是 | 目前只支持 "urlScheme" |
jumpRule.template | 是 | 使用下方变量的 URL 模板 |
查你的 bundle identifier:
mdls -name kMDItemCFBundleIdentifier /Applications/YourApp.app每个变量都会被替换并按 URL query value 编码,下表示例是你的 handler 解码后拿到的值。
| 变量 | 说明 | 示例 |
|---|---|---|
{session_id} | agent 的 session id | ses_abc123def456 |
{cwd} | 工作目录 | /Users/foo/my project |
{tty} | 终端 TTY 设备 | /dev/ttys003 |
{pid} | agent CLI 进程 id | 12345 |
{bundle_id} | 终端的 bundle identifier | com.example.yourterm |
{tmux_pane} | tmux pane id,仅在 tmux 内有值 | %59 |
变量为空时替换成空字符串,不会残留占位符。
单窗口工具可以只用 cwd 匹配;有 tab 或 pane 的终端不要只依赖它,同一目录多个 pane 很常见。
有分屏模型的终端
Section titled “有分屏模型的终端”pane 图属于你的终端,解析也应该由它来做。最小契约:
- 每个 pane、tab、窗口在你的 App 内可以唯一定位。
- handler 接收公开线索:
session、tty、pid、cwd、tmuxPane。 - 用你自己的索引把线索解析成内部目标。
- 找不到目标时只激活 App 或什么都不做,不要新建 pane,也不要猜一个相似的。
建议的解析顺序:能把 agent session 关联到目标就先用 session;记录了每个 pane 的 PTY 路径就用 tty;
再沿进程树用 pid;cwd 只做最后兜底。
Vibe Island 传入的是一组固定的公开变量,私有的 pane 标识不属于这份契约。需要更深的集成,请联系我们讨论内置适配。
# 手动触发 URL,看你的 App 是否响应open "yourterm://focus?session=test-123&cwd=/tmp"跳转轨迹会记录在诊断报告里:Settings > About > Export Diagnostic Report。
我的 App 不是终端,但用户在里面运行 agent,能用吗? 能。用户在你的 App 内运行 Claude Code、Codex 等受支持的 CLI 时,Vibe Island 会记下该 session 对应的 bundle identifier,配上规则就能精确跳转。
还需要给 Vibe Island 发事件吗? 不需要。跳转规则与事件投递彼此独立。如果 App 内运行的是受支持的 CLI,事件早已处理,缺的只是跳转。 自带 agent 的 App 可以在同一个文件里加事件映射,见 App 设置里链接的集成指南。
能把我自己的环境变量传进 URL 吗?
当前版本不能,变量集合是固定的。请在你的 App 内用 session_id、tty 或 pid 反查目标。
我的终端跑在浏览器 tab 里,能接入吗? Electron 或 Tauri 打包的 App 可以,它们有自己的 bundle identifier,也能注册 URL scheme。 纯浏览器 tab 不行:macOS 的 URL scheme 绑定在 bundle identifier 上而不是 tab 上, 而且 Vibe Island 监控的是以本地进程运行的 agent CLI。
多个 fork 共用 bundle identifier 前缀怎么办? 按完整的 bundle identifier 匹配,不做前缀匹配。每个 fork 各放一个文件。
我们支持 tmux,拿到 {tmux_pane} 后该怎么做?
先切到对应 tab,再执行 tmux select-pane -t <pane> 切到具体 pane。
到 github.com/vibeislandapp/vibe-island 提 issue 或发起讨论。