Skip to content

fix(local-cli): AppleScript 被拒时降级到 Launch Services 打开 iTerm#486

Open
47seek wants to merge 2 commits into
deepcoldy:masterfrom
47seek:fix/local-cli-launch-services-fallback
Open

fix(local-cli): AppleScript 被拒时降级到 Launch Services 打开 iTerm#486
47seek wants to merge 2 commits into
deepcoldy:masterfrom
47seek:fix/local-cli-launch-services-fallback

Conversation

@47seek

@47seek 47seek commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

改了什么

daemon 通过 PM2/launchd 后台运行时,osascript 发送 Apple事件的负责进程是 node/PM2 而非 iTerm,macOS TCC 永不弹 Automation 授权弹窗,导致 -1743 错误且自动化设置里找不到 iTerm/Terminal。

新增 openViaCommandFile 降级路径:AppleScript 全部失败后,将命令写入临时 .command 文件并通过 open -a iTerm 打开。Launch Services 无需 Automation 权限,覆盖后台 daemon 场景。

为什么

用户从 iTerm 直接启动 daemon 时,iTerm 是负责进程,Automation 权限正常。改用 PM2 后台运行后,负责进程变为 PM2/node,TCC 无法授权,AppleScript 永久失败。

影响面

  • 仅影响 macOS 本机 CLI 打开流程openLocalCliInIterm
  • AppleScript 成功时行为不变,仅在全部失败后触发降级
  • Linux 路径不受影响(darwin 平台检查在前)
  • 新增 runOpenCommand 依赖注入点,默认走 spawnSync(open, [-a, iTerm, path])
  • 不影响其它 CLI 适配器、后端或会话类型

测试验证

新增 2 个单测覆盖降级成功与降级失败场景,原有 5 个 osascript 调用断言保持不变。

vitest run --project unit test/local-cli-opener.test.ts     → 25 passed
vitest run --project unit test/card-handler-open-local-cli.test.ts → 19 passed

已从 upstream/master 同步后提交,基于 a09ebcf9

daemon 通过 PM2/launchd 后台运行时,osascript 发送 Apple事件的负责进程是
node/PM2 而非 iTerm,macOS TCC 永不弹 Automation 授权弹窗,导致 -1743
错误且自动化设置里找不到 iTerm/Terminal。

新增 openViaCommandFile 降级路径:AppleScript 全部失败后,将命令写入临时
.command 文件并通过 `open -a iTerm` 打开。Launch Services 无需 Automation
权限,覆盖后台 daemon 场景。

影响面:
- 仅影响 macOS 本机 CLI 打开流程(openLocalCliInIterm)
- AppleScript 成功时行为不变,仅在全部失败后触发降级
- Linux 路径不受影响(darwin 平台检查在前)
- 新增 runOpenCommand 依赖注入点,默认走 spawnSync('open', ['-a', 'iTerm', path])

测试:新增 2 个单测覆盖降级成功与降级失败场景,原有 5 个 osascript
调用断言保持不变。vitest run --project unit test/local-cli-opener.test.ts
(25 passed) + test/card-handler-open-local-cli.test.ts (19 passed)。
@47seek
47seek requested a review from deepcoldy as a code owner July 16, 2026 05:18
@deepcoldy

Copy link
Copy Markdown
Owner

Review 总结

感谢 @47seek 贡献这个修复!方案方向正确——daemon 在 PM2/launchd 后台运行时 TCC 永不弹 Automation 授权弹窗,用 Launch Services 降级是合理思路。代码结构清晰,依赖注入设计好,测试覆盖到位。

我们做了两轮独立 review,发现以下问题,建议修复后再合入:

🔴 阻塞:成功路径永久泄漏临时目录

openViaCommandFileopened.ok 后直接 return,未清理 dir(src/services/local-cli-opener.ts:315-321)。每次降级成功留一个含 0755 open-cli.command 的目录(内容含 cwd 和 resume/attach target)。新增成功单测每跑一次也在 /tmp 留目录。

注意:不能简单短延迟 timer 清理——Launch Services 打开是异步的,且 iTerm 对外部脚本默认先弹"OK to run …?"确认框,用户可能很久后才确认或取消。建议让 .command 真正开始执行后自删目录再执行目标命令,同时加较长 TTL/stale sweep 兜底用户取消/未执行的情况。测试需断言清理策略,避免单测自身污染 /tmp

🟡 建议同 PR 补齐

  1. spawnSync 冻结整个 daemon 最长 10 秒(:300-302)——该路径从卡片事件进同一个 Node daemon,open 卡住时所有 bot、WebSocket/事件处理一起停。建议改 Promise + 异步 execFile('/usr/bin/open', ...),用绝对路径,把 result.error(ENOENT/ETIMEDOUT)带进错误信息。
  2. 降级仅覆盖 iTerm,漏 Terminal.app(:301)——TCC 被拒且没装 iTerm 时 fallback 仍必败。建议依次试 iTerm bundle id 和 com.apple.Terminal,或用系统默认 .command handler。
  3. 失败文案过时(:292-297)——base 文案只提"could be opened with AppleScript",建议说明 AppleScript 与 Launch Services 均失败,保留实际 open/文件创建错误。

🟢 额外建议

  • iTerm 默认弹脚本运行确认,open 返回 0 只代表 Launch Services 已受理,不代表 CLI 已执行,这个 UX 建议写进 PR 描述。
  • 当前测试全部 mock 了 open,无法验证核心假设(PM2/launchd 下绕过 TCC、iTerm/Terminal 实际执行 .command)。建议在真实 macOS 后台 daemon 场景 live 测试并记录。

验证结果

  • test/local-cli-opener.test.ts: 25 passed
  • test/card-handler-open-local-cli.test.ts: 19 passed
  • tsc --noEmit: 通过
  • pnpm build: 通过
  • git diff --check: 通过

另外,botmux 项目在飞书有开发者协作群,方便日常沟通、方案讨论和快速 review。如果你愿意加入协作,可以私信我(@deepcoldy)获取入群方式。期待你的更新!🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants