Merge remote main and resolve conflicts

This commit is contained in:
HitMargin
2026-04-04 19:06:31 +08:00
20 changed files with 1093 additions and 463 deletions

25
.vscode/launch.json vendored
View File

@@ -1,13 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "attach",
"name": "Attach to Bun (TUI debug)",
"url": "ws://localhost:8888/2dc3gzl5xot",
"stopOnEntry": false,
"internalConsoleOptions": "neverOpen"
}
]
}
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "attach",
"name": "Attach to Claude Code",
"url": "ws://localhost:8888/2dc3gzl5xot",
"preLaunchTask": "Start Claude Code TUI",
"stopOnEntry": false,
"internalConsoleOptions": "neverOpen"
}
]
}

27
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,27 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Start Claude Code TUI",
"type": "shell",
"command": "bun run dev:inspect",
"isBackground": true,
"presentation": {
"reveal": "always",
"focus": true,
"panel": "dedicated",
"clear": true
},
"problemMatcher": {
"pattern": {
"regexp": "^$"
},
"background": {
"activeOnStart": true,
"beginsPattern": ".*Bun Inspector.*",
"endsPattern": ".*Listening:.*"
}
}
}
]
}

View File

@@ -51,8 +51,8 @@ bun run docs:dev
### Runtime & Build
- **Runtime**: Bun (not Node.js). All imports, builds, and execution use Bun APIs.
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。默认启用 `AGENT_TRIGGERS_REMOTE` feature。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines运行 `src/entrypoints/cli.tsx`。默认启用 `BUDDY``TRANSCRIPT_CLASSIFIER``BRIDGE_MODE``AGENT_TRIGGERS_REMOTE`个 feature。
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。默认启用 `AGENT_TRIGGERS_REMOTE``CHICAGO_MCP``VOICE_MODE` feature。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines运行 `src/entrypoints/cli.tsx`。默认启用 `BUDDY``TRANSCRIPT_CLASSIFIER``BRIDGE_MODE``AGENT_TRIGGERS_REMOTE``CHICAGO_MCP``VOICE_MODE`个 feature。
- **Module system**: ESM (`"type": "module"`), TSX with `react-jsx` transform.
- **Monorepo**: Bun workspaces — internal packages live in `packages/` resolved via `workspace:*`.
- **Lint/Format**: Biome (`biome.json`)。`bun run lint` / `bun run lint:fix` / `bun run format`
@@ -132,8 +132,8 @@ Feature flags control which functionality is enabled at runtime:
- **在代码中使用**: 统一通过 `import { feature } from 'bun:bundle'` 导入,调用 `feature('FLAG_NAME')` 返回 `boolean`。**不要**在 `cli.tsx` 或其他文件里自己定义 `feature` 函数或覆盖这个 import。
- **启用方式**: 通过环境变量 `FEATURE_<FLAG_NAME>=1`。例如 `FEATURE_BUDDY=1 bun run dev` 启用 BUDDY 功能。
- **Dev 默认 features**: `BUDDY``TRANSCRIPT_CLASSIFIER``BRIDGE_MODE``AGENT_TRIGGERS_REMOTE`(见 `scripts/dev.ts`)。
- **Build 默认 features**: `AGENT_TRIGGERS_REMOTE`(见 `build.ts`)。
- **Dev 默认 features**: `BUDDY``TRANSCRIPT_CLASSIFIER``BRIDGE_MODE``AGENT_TRIGGERS_REMOTE``CHICAGO_MCP``VOICE_MODE`(见 `scripts/dev.ts`)。
- **Build 默认 features**: `AGENT_TRIGGERS_REMOTE``CHICAGO_MCP``VOICE_MODE`(见 `build.ts`)。
- **常见 flag**: `BUDDY`, `DAEMON`, `BRIDGE_MODE`, `BG_SESSIONS`, `PROACTIVE`, `KAIROS`, `VOICE_MODE`, `FORK_SUBAGENT`, `SSH_REMOTE`, `DIRECT_CONNECT`, `TEMPLATES`, `CHICAGO_MCP`, `BYOC_ENVIRONMENT_RUNNER`, `SELF_HOSTED_RUNNER`, `COORDINATOR_MODE`, `UDS_INBOX`, `LODESTONE`, `ABLATION_BASELINE` 等。
- **类型声明**: `src/types/internal-modules.d.ts` 中声明了 `bun:bundle` 模块的 `feature` 函数签名。
@@ -143,13 +143,45 @@ Feature flags control which functionality is enabled at runtime:
| Module | Status |
|--------|--------|
| Computer Use (`@ant/*`) | Stub packages in `packages/@ant/` |
| `*-napi` packages (audio, image, url, modifiers) | Stubs in `packages/` (except `color-diff-napi` which is fully implemented) |
| Computer Use (`@ant/*`) | Restored — `computer-use-swift`, `computer-use-input`, `computer-use-mcp`, `claude-for-chrome-mcp` 均有完整实现macOS + Windows 可用Linux 后端待完成 |
| `*-napi` packages | `audio-capture-napi``image-processor-napi` 已恢复实现;`color-diff-napi` 完整实现;`url-handler-napi``modifiers-napi` 仍为 stub |
| Voice Mode | Restored — `src/voice/``src/hooks/useVoiceIntegration.tsx``src/services/voiceStreamSTT.ts`Push-to-Talk 语音输入(需 Anthropic OAuth |
| OpenAI 兼容层 | Restored — `src/services/api/openai/`,支持 Ollama/DeepSeek/vLLM 等任意 OpenAI 协议端点,通过 `CLAUDE_CODE_USE_OPENAI=1` 启用 |
| Analytics / GrowthBook / Sentry | Empty implementations |
| Magic Docs / Voice Mode / LSP Server | Removed |
| Magic Docs / LSP Server | Removed |
| Plugins / Marketplace | Removed |
| MCP OAuth | Simplified |
### Computer Use
Feature flag `CHICAGO_MCP`dev/build 默认启用。实现跨平台屏幕操控macOS + Windows 可用Linux 待完成)。
- **`packages/@ant/computer-use-mcp/`** — MCP server注册截图/键鼠/剪贴板/应用管理工具
- **`packages/@ant/computer-use-input/`** — 键鼠模拟dispatcher + per-platform backend`backends/darwin.ts``win32.ts``linux.ts`
- **`packages/@ant/computer-use-swift/`** — 截图 + 应用管理,同样 dispatcher + per-platform backend
- **`packages/@ant/claude-for-chrome-mcp/`** — Chrome 浏览器控制(独立于 Computer Use通过 `--chrome` CLI 参数启用)
详见 `docs/features/computer-use.md`
### Voice Mode
Feature flag `VOICE_MODE`dev/build 默认启用。Push-to-Talk 语音输入,音频通过 WebSocket 流式传输到 Anthropic STTNova 3。需要 Anthropic OAuth非 API key
- **`src/voice/voiceModeEnabled.ts`** — 三层门控feature flag + GrowthBook + OAuth auth
- **`src/hooks/useVoice.ts`** — React hook 管理录音状态和 WebSocket 连接
- **`src/services/voiceStreamSTT.ts`** — STT WebSocket 流式传输
详见 `docs/features/voice-mode.md`
### OpenAI 兼容层
通过 `CLAUDE_CODE_USE_OPENAI=1` 环境变量启用,支持任意 OpenAI Chat Completions 协议端点Ollama、DeepSeek、vLLM 等)。流适配器模式:在 `queryModel()` 中将 Anthropic 格式请求转为 OpenAI 格式,再将 SSE 流转换回 `BetaRawMessageStreamEvent`,下游代码完全不改。
- **`src/services/api/openai/`** — client、消息/工具转换、流适配、模型映射
- **`src/utils/model/providers.ts`** — 添加 `'openai'` provider 类型(最高优先级)
关键环境变量:`CLAUDE_CODE_USE_OPENAI``OPENAI_API_KEY``OPENAI_BASE_URL``OPENAI_MODEL``OPENAI_MODEL_MAP`。详见 `docs/plans/openai-compatibility.md`
### Key Type Files
- **`src/types/global.d.ts`** — Declares `MACRO`, `BUILD_TARGET`, `BUILD_ENV` and internal Anthropic-only identifiers.

View File

@@ -1,5 +1,42 @@
# DEV-LOG
## /dream 手动触发 + DreamTask 类型补全 (2026-04-04)
`/dream` 命令从 KAIROS feature gate 中解耦,作为 bundled skill 无条件注册;补全 DreamTask 类型存根。
**新增文件:**
| 文件 | 说明 |
|------|------|
| `src/skills/bundled/dream.ts` | `/dream` skill 注册,调用 `buildConsolidationPrompt()` 生成整理提示词 |
**修改文件:**
| 文件 | 变更 |
|------|------|
| `src/skills/bundled/index.ts` | 导入并注册 `registerDreamSkill()` |
| `src/components/tasks/src/tasks/DreamTask/DreamTask.ts` | `any` 存根 → 从 `src/tasks/DreamTask/DreamTask.js` 重新导出完整类型 |
**新增文档:**
| 文件 | 说明 |
|------|------|
| `docs/features/auto-dream.md` | Auto Dream 原理、触发机制、使用场景完整说明 |
---
## Computer Use macOS 适配修复 (2026-04-04)
**分支**: `feature/computer-use/mac-support`
- **darwin.ts** — 应用枚举改用 Spotlight `mdfind` + `mdls`,获取真实 bundleId旧方案合成 `com.app.xxx`),覆盖 `/Applications` + `/System/Applications` + CoreServices
- **index.ts** — 新增 `hotkey` backend fallback非原生模块不崩溃
- **toolCalls.ts** — `resolveRequestedApps()` 新增子串模糊匹配(`"Chrome"``"Google Chrome"`
- **hostAdapter.ts** — `ensureOsPermissions()` 检查 `cu.tcc` 存在性,跨平台 JS backend 安全降级
- **测试**: 17 个 MCP 工具中 10 个完全通过6 个在 full tier 应用上通过IDE click tier 受限为预期行为),`screenshot` 未返回图片(疑似屏幕录制权限问题)
---
## Computer Use Windows 增强:窗口绑定截图 + UI Automation + OCR (2026-04-03)
在三平台基础实现之上,利用 Windows 原生 API 增强 Computer Use 的 Windows 专属能力。
@@ -413,3 +450,4 @@ GrowthBook 功能开关系统原为 Anthropic 内部构建设计,硬编码 SDK
```
非空字段才写入,保存后立即生效(`onDone()` 触发 `onChangeAPIKey()` 刷新 API 客户端)。

View File

@@ -34,12 +34,19 @@
- [x] 关闭自动更新;
- [x] 添加自定义 sentry 错误上报支持 [文档](https://ccb.agent-aura.top/docs/internals/sentry-setup)
- [x] 添加自定义 GrowthBook 支持 (GB 也是开源的, 现在你可以配置一个自定义的遥控平台) [文档](https://ccb.agent-aura.top/docs/internals/growthbook-adapter)
- [x] 自定义 login 模式, 大家可以用这个配置 Claude 的模型!
- [x] 自定义 login 模式, 大家可以用这个配置 Claude 的模型! [文档](https://ccb.agent-aura.top/docs/features/custom-platform-login)
- [x] Remote Control / Bridge Mode 支持 [文档](https://ccb.agent-aura.top/docs/features/bridge-mode)
- [x] 修复搜索工具的 rg 缺失问题(需要重新 bun i)
- [x] OpenAI 接口兼容! /login 然后配置 OpenAI 平台即可!
- [x] Chrome use 支持(暂时浏览器插件要订阅权限,万恶的牢 A) 感谢 @amDosion
- [x] Computer use 支持 感谢 @amDosion
- [x] /voice 支持 @amDosion
- [x] OpenAI 接口兼容! /login 然后配置 OpenAI 平台即可! [文档](https://ccb.agent-aura.top/docs/plans/openai-compatibility)
- [x] Any Use
- [x] 由于 Chrome Use 和 Computer Use 原本都是未完全验证的能力, 还是比较建议大家用社区里面的 MCP 支持
- [x] Chrome use 支持 (浏览器插件要订阅权限 ) 感谢 @amDosion [文档](https://ccb.agent-aura.top/docs/features/claude-in-chrome-mcp)
- [x] 普通用户可以使用 [chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp/) 替代, 比较不那么折腾
- [x] Computer use 支持 感谢 @amDosion [文档](https://ccb.agent-aura.top/docs/features/computer-use)
- [x] Mac 上可以用这个项目 [computer-use-mcp](https://github.com/domdomegg/computer-use-mcp)
- 注意这个库的命名方式与官方冲突了, 需要改为 `claude mcp add --scope user --transport stdio computer-use-mcp -- npx -y computer-use-mcp`
- [x] /voice 支持 @amDosion [文档](https://ccb.agent-aura.top/docs/features/voice-mode)
- [x] /dream 记忆整理命令(手动 + 自动后台触发) [文档](https://ccb.agent-aura.top/docs/features/auto-dream)
- [ ] V6 大规模重构石山代码, 全面模块分包
- [ ] V6 将会为全新分支, 届时 main 分支将会封存为历史版本
@@ -65,6 +72,12 @@
bun install
```
国内对 github 网络较差的,可以使用这个环境变量
```bash
DEFAULT_RELEASE_BASE=https://ghproxy.net/https://github.com/microsoft/ripgrep-prebuilt/releases/download/v15.0.1
```
### 运行
```bash

182
docs/features/auto-dream.md Normal file
View File

@@ -0,0 +1,182 @@
# Auto Dream — 自动记忆整理
## 概述
Auto Dream 是 Claude Code 的后台记忆整合机制。它在会话间自动审查、组织和修剪持久化记忆文件,确保未来会话能快速获得准确的上下文。
记忆系统存储在文件系统中(默认 `~/.claude/projects/<project-slug>/memory/`),由 `MEMORY.md` 索引文件和若干主题文件(如 `user_language.md``project_overview.md`组成。随着会话积累记忆会变得过时、冗余或矛盾——Dream 负责清理这些堆积。
## 架构
### 核心模块
| 模块 | 路径 | 职责 |
|------|------|------|
| 调度器 | `src/services/autoDream/autoDream.ts` | 时间/会话/锁三重门控,触发 forked agent |
| 配置 | `src/services/autoDream/config.ts` | 读取 `isAutoDreamEnabled()` 开关 |
| 提示词 | `src/services/autoDream/consolidationPrompt.ts` | 构建 4 阶段整理提示词 |
| 锁文件 | `src/services/autoDream/consolidationLock.ts` | PID 锁 + mtime 作为 `lastConsolidatedAt` |
| 任务 UI | `src/tasks/DreamTask/DreamTask.ts` | 后台任务注册footer pill + Shift+Down 可见 |
| 手动入口 | `src/skills/bundled/dream.ts` | `/dream` 命令,无条件可用 |
### 记忆路径解析
优先级(`src/memdir/paths.ts`
1. `CLAUDE_COWORK_MEMORY_PATH_OVERRIDE` 环境变量(完整路径覆盖)
2. `autoMemoryDirectory` 设置项(`settings.json`,支持 `~/` 展开)
3. 默认:`<memoryBase>/projects/<sanitized-git-root>/memory/`
其中 `memoryBase` = `CLAUDE_CODE_REMOTE_MEMORY_DIR``~/.claude`
## 触发机制
### 自动触发Auto Dream
每个对话轮次结束后,`executeAutoDream()` 按顺序检查三重门控:
```
┌─────────────────────────────────────────────────────┐
│ Gate 1: 全局开关 │
│ isAutoMemoryEnabled() && isAutoDreamEnabled() │
│ 排除: KAIROS 模式 / Remote 模式 │
├─────────────────────────────────────────────────────┤
│ Gate 2: 时间门控 │
│ hoursSince(lastConsolidatedAt) >= minHours │
│ 默认: 24 小时 │
├─────────────────────────────────────────────────────┤
│ Gate 3: 会话门控 │
│ sessionsTouchedSince(lastConsolidatedAt) >= minSessions │
│ 默认: 5 个会话(排除当前会话) │
├─────────────────────────────────────────────────────┤
│ Lock: PID 锁文件 │
│ .consolidate-lock (mtime = lastConsolidatedAt) │
│ 死进程检测 + 1 小时过期 │
└─────────────────────────────────────────────────────┘
```
全部通过后,以 **forked agent**(受限子代理)方式运行整理任务:
- Bash 工具限制为只读命令(`ls``grep``cat` 等)
- 只能读写记忆目录内的文件
- 用户可在 Shift+Down 后台任务面板中查看进度或终止
### 手动触发(`/dream` 命令)
通过 `/dream` 命令随时触发,无门控限制:
- 在主循环中运行(非 forked agent拥有完整工具权限
- 用户可实时观察操作过程
- 执行前自动更新锁文件 mtime
### 配置开关
| 开关 | 位置 | 作用 |
|------|------|------|
| `autoDreamEnabled` | `settings.json` | `true`/`false` 显式开关 |
| `autoMemoryEnabled` | `settings.json` | 总开关,关闭后所有记忆功能禁用 |
| `CLAUDE_CODE_DISABLE_AUTO_MEMORY` | 环境变量 | `1`/`true` 关闭所有记忆功能 |
| `tengu_onyx_plover` | GrowthBook | 官方远程配置,控制 `enabled`/`minHours`/`minSessions` |
默认值(无 GrowthBook 连接时):
```typescript
minHours: 24 // 距上次整理至少 24 小时
minSessions: 5 // 至少有 5 个新会话
```
## 整理流程4 阶段)
Dream agent 执行的提示词包含 4 个阶段:
### Phase 1 — 定位Orient
- `ls` 记忆目录,查看现有文件
- 读取 `MEMORY.md` 索引
- 浏览现有主题文件,避免重复创建
### Phase 2 — 采集信号Gather
按优先级收集新信息:
1. **日志文件**`logs/YYYY/MM/YYYY-MM-DD.md`KAIROS 模式下的追加式日志)
2. **过时记忆** — 与当前代码库状态矛盾的事实
3. **会话记录** — 窄关键词 grep JSONL 文件(不全文读取)
### Phase 3 — 整合Consolidate
- 合并新信号到现有主题文件,而非创建近似重复
- 将相对日期("昨天"、"上周")转为绝对日期
- 删除被推翻的事实
### Phase 4 — 修剪与索引Prune
- `MEMORY.md` 保持在 200 行以内、25KB 以内
- 每条索引项一行,不超过 150 字符
- 移除过时/错误/被取代的指针
## 记忆类型
记忆系统使用 4 种类型(`src/memdir/memoryTypes.ts`
| 类型 | 用途 | 示例 |
|------|------|------|
| `user` | 用户角色、偏好、知识 | 用户是高级后端工程师,偏好中文交流 |
| `feedback` | 工作方式指导 | 不要 mock 数据库测试;代码审查用 bundled PR |
| `project` | 项目上下文(非代码可推导的) | 合并冻结从 3 月 5 日开始;认证重写是合规需求 |
| `reference` | 外部系统指针 | Linear INGEST 项目跟踪 pipeline bugs |
**不保存的内容**代码模式、架构、文件路径可从代码推导Git 历史(`git log` 权威);调试方案(代码中已有)。
## 锁文件机制
`.consolidate-lock` 文件位于记忆目录内:
- **文件内容**:持有者 PID
- **mtime**:即 `lastConsolidatedAt` 时间戳
- **过期**1 小时(防 PID 复用)
- **竞态处理**:双进程同时写入时,后读验证 PID失败者退出
- **回滚**forked agent 失败或被用户终止时mtime 回退到获取前的值
## 使用场景
### 场景 1日常开发中的自动整理
开发者连续多天使用 Claude Code 处理不同任务。Auto Dream 在积累 5+ 个会话且距上次整理 24 小时后自动触发,整合分散在多次会话中的用户偏好和项目决策。
### 场景 2手动整理记忆
用户发现 Claude 重复犯相同错误或遗忘之前的决策。输入 `/dream` 立即触发整理,无需等待自动触发周期。
### 场景 3新会话快速上下文
新会话启动时,`MEMORY.md` 被加载到上下文中。经过 Dream 整理的记忆文件结构清晰、信息准确,让 Claude 快速了解用户和项目。
### 场景 4KAIROS 模式下的日志蒸馏
KAIROS长驻助手模式agent 以追加方式写入日期日志文件。Dream 负责将这些日志蒸馏为主题文件和 `MEMORY.md` 索引。
## 与其他系统的关系
```
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ 会话交互 │────▶│ 记忆写入 │────▶│ MEMORY.md │
│ (主 agent) │ │ (即时保存) │ │ + 主题文件 │
└─────────────┘ └──────────────┘ └───────┬───────┘
┌───────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐
│ Auto Dream │────▶│ 整理/修剪 │
│ (后台触发) │ │ 去重/纠错 │
└──────────────┘ └──────────────┘
┌──────────────┐
│ /dream 命令 │
│ (手动触发) │
└──────────────┘
```
- **extractMemories**`src/services/extractMemories/`每轮次结束时从对话中提取新记忆并写入。Dream 不负责提取,只负责整理。
- **CLAUDE.md**:项目级指令文件,加载到上下文中但不属于记忆系统。
- **Team Memory**`TEAMMEM` feature团队共享记忆目录与个人记忆使用相同的 Dream 机制。

View File

@@ -1,243 +1,137 @@
# Claude in Chrome MCP — 恢复计划
# Claude in Chrome — 用户操作指南
更新时间2026-04-03
参考项目:`E:\源码\claude-code-source-main\claude-code-source-main`
## 1. 功能简介
## 1. 功能概述
Claude in Chrome 让 Claude Code 直接控制你的 Chrome 浏览器。你可以用自然语言让 Claude 帮你:
Claude in Chrome 让 Claude Code CLI 通过 MCP 协议控制用户的 Chrome 浏览器:导航网页、填写表单、截图、录制 GIF、读取 DOM、执行 JS、监控网络请求和控制台日志。
- 打开网页、导航、前进后退
- 填写表单、上传图片
- 截图、录制 GIF
- 读取页面内容DOM、纯文本
- 执行 JavaScript
- 监控网络请求和控制台日志
- 管理标签页
通信方式有两种:
- **本地 Socket**Chrome 扩展通过 Native Messaging Host 与 CLI 建立 Unix socket 连接
- **Bridge WebSocket**:通过 Anthropic 的 bridge 服务中转,支持远程浏览器
## 2. 前置条件
## 2. 完整加载链路
| 条件 | 说明 |
|------|------|
| Claude Code 订阅 | 需要 Claude Pro、Max 或 Team 订阅,浏览器插件功能不向免费用户开放 |
| Chrome 浏览器 | 需已安装 Google Chrome |
| Claude in Chrome 扩展 | 从 Chrome Web Store 安装(`claude.ai/chrome` |
| Claude Code CLI | 已通过 `bun run dev` 或构建产物运行 |
```
CLI 启动
src/main.tsx:1003
.option('--chrome', 'Enable Claude in Chrome integration')
src/main.tsx:1522-1527
setChromeFlagOverride(chromeOpts.chrome)
src/utils/claudeInChrome/setup.ts
shouldEnableClaudeInChrome()
├── --chrome flag → true
├── --no-chrome flag → false
├── 非交互模式 → false
├── 环境变量 CLAUDE_CODE_DISABLE_CHROME → false
├── 配置 claudeInChromeDefaultEnabled → true/false
└── Chrome 扩展已安装 + GrowthBook tengu_chrome_auto_enable → auto
src/utils/claudeInChrome/setup.ts
setupClaudeInChrome()
├── 生成 MCP server 配置
└── 返回 mcpConfig + allowedTools
src/utils/claudeInChrome/mcpServer.ts
import { createClaudeForChromeMcpServer } from '@ant/claude-for-chrome-mcp'
packages/@ant/claude-for-chrome-mcp/src/index.ts ← 当前是 STUB
export function createClaudeForChromeMcpServer() { return null }
export const BROWSER_TOOLS = []
## 3. 启用方式
### Dev 模式
```bash
bun run dev -- --chrome
```
## 3. 阻塞点清单
启动后 Claude 会自动检测 Chrome 扩展是否已安装,并注册浏览器控制工具。
| # | 阻塞点 | 位置 | 状态 |
|---|--------|------|------|
| ① | `@ant/claude-for-chrome-mcp` 是 stub | `packages/@ant/claude-for-chrome-mcp/src/index.ts` | **6 行空壳,返回 null** |
| ② | 缺少完整实现7 个文件3038 行) | `packages/@ant/claude-for-chrome-mcp/src/` | 只有 1 个 stub 文件 |
### 构建产物
**不需要任何 feature flag**`/chrome` 命令无条件注册在 `src/commands.ts:264`
```bash
node dist/cli.js --chrome
```
**不需要改 `src/` 下任何文件** — 以下文件全部与参考项目 0 行差异:
- `src/utils/claudeInChrome/setup.ts`
- `src/utils/claudeInChrome/mcpServer.ts`
- `src/utils/claudeInChrome/common.ts`
- `src/utils/claudeInChrome/chromeNativeHost.ts`
- `src/utils/claudeInChrome/prompt.ts`
- `src/utils/claudeInChrome/setupPortable.ts`
- `src/utils/claudeInChrome/toolRendering.tsx`
- `src/commands/chrome/index.ts`
- `src/commands/chrome/chrome.tsx`(仅 sourcemap 差异)
- `src/skills/bundled/claudeInChrome.ts`
### 禁用
## 4. 参考项目完整实现清单
```bash
bun run dev -- --no-chrome
```
参考项目路径:`deps/@ant/claude-for-chrome-mcp/src/`
或在 REPL 中通过 `/chrome` 命令切换启用/禁用状态。
| 文件 | 行数 | 职责 |
|------|------|------|
| `index.ts` | 15 | 导出入口:`createBridgeClient``BROWSER_TOOLS``createChromeSocketClient``createClaudeForChromeMcpServer``localPlatformLabel` + 类型导出 |
| `types.ts` | 134 | 类型定义:`Logger``PermissionMode``BridgeConfig``ChromeExtensionInfo``ClaudeForChromeContext``SocketClient``BridgePermissionRequest/Response``PermissionOverrides` |
| `browserTools.ts` | 546 | 17 个浏览器工具定义MCP tool schema |
| `mcpServer.ts` | 96 | MCP Server 创建:注册 `ListTools`/`CallTool` handler选择 socket/bridge 传输 |
| `mcpSocketClient.ts` | 493 | Unix Socket 客户端:连接 Chrome Native Messaging HostJSON-RPC 通信 |
| `mcpSocketPool.ts` | 327 | Socket 连接池:多 Chrome profile 支持,按 tabId 路由 |
| `bridgeClient.ts` | 1126 | Bridge WebSocket 客户端:连接 Anthropic bridge 服务,扩展发现、设备配对、权限管理 |
| `toolCalls.ts` | 301 | 工具调用路由:连接状态处理、结果转换、权限模式切换、浏览器切换 |
### 通过配置默认启用
### 17 个浏览器工具
在 Claude Code 设置中将 `claudeInChromeDefaultEnabled` 设为 `true`,以后启动无需加 `--chrome` 参数。
| 工具名 | 功能 |
|--------|------|
| `javascript_tool` | 在页面上下文执行 JavaScript |
| `read_page` | 获取页面可访问性树DOM |
| `find` | 自然语言搜索页面元素 |
## 4. 使用流程
1. **启动 CLI** — 加 `--chrome` 参数启动 Claude Code
2. **确认连接** — REPL 中输入 `/chrome`,查看扩展状态是否显示 "Installed / Connected"
3. **开始对话** — 正常与 Claude 对话,当需要操作浏览器时直接说,例如:
- "打开 https://example.com 并截图"
- "在当前页面搜索关键词 xxx"
- "填写登录表单,用户名 admin"
- "帮我录制当前操作的 GIF"
4. **权限审批** — 首次执行浏览器操作时Claude 会请求你的确认
5. **操作完成** — Claude 完成操作后会返回结果(截图、文本、执行结果等)
## 5. 可用操作
### 页面交互
| 操作 | 说明 |
|------|------|
| `navigate` | 导航到指定 URL或前进/后退 |
| `computer` | 鼠标点击、移动、拖拽、键盘输入、截图等13 种 action |
| `form_input` | 填写表单字段 |
| `computer` | 鼠标键盘操作 + 截图13 种 action |
| `navigate` | URL 导航 / 前进后退 |
| `resize_window` | 调整浏览器窗口尺寸 |
| `gif_creator` | GIF 录制和导出 |
| `upload_image` | 图片上传到文件输入框或拖拽区域 |
| `get_page_text` | 提取页面纯文本 |
| `upload_image` | 上传图片到文件输入框或拖拽区域 |
| `javascript_tool` | 在页面上下文执行 JavaScript |
### 页面读取
| 操作 | 说明 |
|------|------|
| `read_page` | 获取页面可访问性树DOM 结构) |
| `get_page_text` | 提取页面纯文本内容 |
| `find` | 用自然语言搜索页面元素 |
### 标签页管理
| 操作 | 说明 |
|------|------|
| `tabs_context_mcp` | 获取当前标签组信息 |
| `tabs_create_mcp` | 创建新标签页 |
| `update_plan` | 向用户提交操作计划供审批 |
### 监控与调试
| 操作 | 说明 |
|------|------|
| `read_console_messages` | 读取浏览器控制台日志 |
| `read_network_requests` | 读取网络请求 |
| `read_network_requests` | 读取网络请求记录 |
### 其他
| 操作 | 说明 |
|------|------|
| `resize_window` | 调整浏览器窗口尺寸 |
| `gif_creator` | 录制 GIF 并导出 |
| `shortcuts_list` | 列出可用快捷方式 |
| `shortcuts_execute` | 执行快捷方式 |
| `switch_browser` | 切换到其他 Chrome 浏览器(仅 bridge 模式) |
| `update_plan` | 向你提交操作计划供审批 |
| `switch_browser` | 切换到其他 Chrome 浏览器(仅 Bridge 模式) |
### 外部依赖
## 6. 通信模式
| 依赖 | 用途 | 我们项目是否已有 |
|------|------|----------------|
| `ws` | WebSocket 客户端bridge 模式) | ✅ 有 |
| `@modelcontextprotocol/sdk` | MCP Server + 类型 | ✅ 有 |
| `fs`/`net`/`os`/`path` | Node.js 内置 | ✅ |
Claude in Chrome 支持两种与浏览器通信的方式:
## 5. 修复步骤
### 本地 Socket默认
### 步骤 1复制完整实现到 stub 包目录
Chrome 扩展通过 Native Messaging Host 与 CLI 建立 Unix socket 连接。适用于本地开发,无需额外配置。
```bash
# 从参考项目复制 7 个文件(覆盖现有的 1 个 stub
cp "E:/源码/claude-code-source-main/claude-code-source-main/deps/@ant/claude-for-chrome-mcp/src/"*.ts \
"E:/源码/Claude-code-bast/packages/@ant/claude-for-chrome-mcp/src/"
```
### Bridge WebSocket
复制后 `packages/@ant/claude-for-chrome-mcp/src/` 应包含 8 个文件:
通过 Anthropic 的 bridge 服务中转,支持远程操控浏览器。需要 claude.ai OAuth 登录。
```
packages/@ant/claude-for-chrome-mcp/src/
├── index.ts ← 覆盖 stub15 行,导出入口)
├── types.ts ← 新增134 行)
├── browserTools.ts ← 新增546 行)
├── mcpServer.ts ← 新增96 行)
├── mcpSocketClient.ts ← 新增493 行)
├── mcpSocketPool.ts ← 新增327 行)
├── bridgeClient.ts ← 新增1126 行)
└── toolCalls.ts ← 新增301 行)
```
## 7. 常见问题
### 步骤 2验证构建
### 扩展显示未安装
```bash
bun run build
```
确认已从 Chrome Web Store 安装 "Claude in Chrome" 扩展,安装后重启浏览器。
不需要改 `scripts/dev.ts``build.ts`(无 feature flag
### 工具未出现在工具列表
### 步骤 3功能验证
检查启动时是否加了 `--chrome` 参数,或通过 `/chrome` 命令确认状态。
```bash
# 启动(手动启用 chrome
bun run dev -- --chrome
### 连接超时
# 在 REPL 中:
# 1. /chrome 命令应显示 Chrome 设置菜单
# 2. 如果 Chrome 扩展已安装 → 状态显示 "Enabled"
# 3. 如果未安装 → 提示安装扩展链接
```
确保 Chrome 浏览器正在运行且扩展已启用。Native Messaging Host 在扩展安装时自动注册,如果重装过扩展需要重启浏览器。
## 6. 验证测试项
### 不使用 Chrome 功能时
### 6.1 构建验证
| 测试项 | 预期结果 | 验证命令 |
|--------|---------|---------|
| build 成功 | 无报错 | `bun run build` |
| BROWSER_TOOLS 非空 | 产物中包含 17 个工具定义 | `grep "javascript_tool" dist/*.js` |
| createClaudeForChromeMcpServer 非 null | 产物中包含 MCP Server 创建逻辑 | `grep "ListToolsRequestSchema" dist/*.js` |
| Bridge WebSocket 逻辑在产物中 | 包含 bridge 连接代码 | `grep "bridge.claudeusercontent.com" dist/*.js` |
### 6.2 命令注册验证
| 测试项 | 预期结果 |
|--------|---------|
| `/chrome` 命令可见 | REPL 中输入 `/chrome` 显示设置菜单 |
| `--chrome` 参数可用 | `bun run dev -- --chrome` 不报错 |
| `--no-chrome` 参数可用 | `bun run dev -- --no-chrome` 不报错 |
### 6.3 MCP Server 验证(需要 Chrome 扩展)
| 测试项 | 预期结果 |
|--------|---------|
| Chrome 扩展检测 | 已安装扩展时 `/chrome` 显示 "Extension: Installed" |
| Socket 连接 | 扩展连接后 MCP tools 可用 |
| BROWSER_TOOLS 注册 | `tabs_context_mcp` 等 17 个工具在 MCP 工具列表中可见 |
### 6.4 工具功能验证(需要 Chrome 扩展 + 连接)
| 测试项 | 预期结果 |
|--------|---------|
| `tabs_context_mcp` | 返回当前标签组信息 |
| `navigate` | 能导航到指定 URL |
| `computer` + `screenshot` | 返回页面截图 |
| `read_page` | 返回 DOM 可访问性树 |
| `javascript_tool` | 执行 JS 并返回结果 |
### 6.5 不影响现有功能
| 测试项 | 预期结果 |
|--------|---------|
| 不带 `--chrome` 启动 | 正常运行,无 chrome 相关报错 |
| `/voice` 命令 | 不受影响 |
| `/schedule` 命令 | 不受影响 |
| `bun test` | 现有测试全部通过 |
## 7. 改动总结
| 操作 | 文件 | 说明 |
|------|------|------|
| 覆盖 stub | `packages/@ant/claude-for-chrome-mcp/src/index.ts` | 6 行 stub → 15 行完整导出 |
| 新增 | `packages/@ant/claude-for-chrome-mcp/src/types.ts` | 134 行类型定义 |
| 新增 | `packages/@ant/claude-for-chrome-mcp/src/browserTools.ts` | 546 行17 个工具定义 |
| 新增 | `packages/@ant/claude-for-chrome-mcp/src/mcpServer.ts` | 96 行 MCP Server |
| 新增 | `packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts` | 493 行 Socket 客户端 |
| 新增 | `packages/@ant/claude-for-chrome-mcp/src/mcpSocketPool.ts` | 327 行连接池 |
| 新增 | `packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts` | 1126 行 Bridge 客户端 |
| 新增 | `packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts` | 301 行工具调用路由 |
**不改动**`src/` 下所有文件(已与参考项目一致)、`scripts/dev.ts``build.ts`
## 8. 运行时依赖
| 依赖 | 必需? | 说明 |
|------|--------|------|
| Chrome 浏览器 | 是 | 需安装 Chrome |
| Claude in Chrome 扩展 | 是 | 从 https://claude.ai/chrome 安装 |
| claude.ai OAuth 登录 | Bridge 模式需要 | 本地 Socket 模式不需要 |
| Native Messaging Host | 本地 Socket 需要 | 扩展安装时自动注册 |
## 9. 与 /voice、/schedule 恢复方式对比
| 项 | `/schedule` | `/voice` | Claude in Chrome |
|---|---|---|---|
| 编译开关 | `AGENT_TRIGGERS_REMOTE` | `VOICE_MODE` | **无需** |
| 改 dev.ts/build.ts | ✅ | ✅ | **不需要** |
| 缺失的 vendor 二进制 | 无 | `.node` 文件 | 无 |
| 需要替换的 stub | 无 | `audio-capture-napi` | `@ant/claude-for-chrome-mcp`7 个文件) |
| 改动 src/ 源码 | 无 | 无 | 无 |
| 平台限制 | 无 | 需原生 `.node` | 需 Chrome 浏览器 |
不带 `--chrome` 参数正常启动即可,不会加载任何浏览器相关模块,不影响其他功能。

View File

@@ -0,0 +1,277 @@
# Computer Use MCP 工具测试报告
> 测试日期: 2026-04-04
> 测试环境: macOS Darwin 25.4.0, Cursor (IDE tier: click)
> MCP Server: `@ant/computer-use-mcp`
## 工具总览
共 17 个工具(含 batch 复合操作),分为 5 大类:
| 类别 | 工具 | 数量 |
|------|------|------|
| 截图/显示 | `screenshot`, `switch_display`, `zoom` | 3 |
| 鼠标操作 | `left_click`, `right_click`, `double_click`, `triple_click`, `middle_click`, `left_click_drag`, `mouse_move` | 7 |
| 键盘操作 | `key`, `type`, `hold_key` | 3 |
| 状态查询 | `cursor_position`, `request_access` | 2 |
| 复合/辅助 | `computer_batch`, `wait` | 2 |
---
## 测试结果
### 1. 权限管理
#### `request_access` — 请求应用访问权限
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 行为 | 弹出系统对话框请求用户授权,支持批量申请多个应用 |
| 返回 | `{ granted: [...], denied: [...], tierGuidance: "..." }` |
| 权限分级 | `click`(仅点击), `full`(完整控制) |
| 说明 | IDE 类应用Cursor、VSCode、Terminal默认授予 `click` tier限制键盘输入和右键操作系统应用System Settings授予 `full` tier |
#### 已授权应用
| 应用 | Tier | 能力 |
|------|------|------|
| Cursor | click | 可见 + 纯左键点击(无键盘输入、右键、修饰键点击、拖拽) |
| Terminal | click | 同上 |
| System Settings | full | 完整控制(键鼠、拖拽等) |
| Finder | — | 已授权 |
---
### 2. 截图与显示
#### `screenshot` — 截取屏幕截图
| 项目 | 结果 |
|------|------|
| 状态 | ⚠️ 部分通过 |
| 执行 | 工具成功执行,返回 `ok: true` |
| 图片 | **未返回可视图片内容**output 为空字符串) |
| `save_to_disk` | 设置后仍无输出 |
| 分析 | 可能原因:(1) macOS 屏幕录制权限未授予;(2) 当前前台应用未被过滤导致截图为空;(3) MCP 传输层未正确编码图片数据 |
| 建议 | 检查 **系统设置 → 隐私与安全性 → 屏幕录制** 是否授权给运行 Claude Code 的应用 |
#### `switch_display` — 切换显示器
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 行为 | 接受显示器名称或 `"auto"`(自动选择) |
| 返回 | 确认消息 |
#### `zoom` — 区域放大截图
| 项目 | 结果 |
|------|------|
| 状态 | ⏭️ 跳过 |
| 原因 | 依赖 `screenshot` 返回的图片坐标,截图未返回图片无法测试 |
---
### 3. 鼠标操作
> 以下测试在 Cursor 窗口上执行tier: click
#### `mouse_move` — 移动鼠标
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 输入 | `coordinate: [500, 500]` |
| 返回 | `"Moved."` |
#### `left_click` — 左键单击
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 输入 | `coordinate: [500, 500]` |
| 返回 | `"Clicked."` |
#### `double_click` — 双击
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 输入 | `coordinate: [500, 500]` |
| 返回 | `"Clicked."` |
#### `triple_click` — 三击
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 输入 | `coordinate: [500, 500]` |
| 返回 | `"Clicked."` |
#### `right_click` — 右键点击
| 项目 | 结果 |
|------|------|
| 状态 | ⚠️ 受 tier 限制 |
| Cursor (click tier) | ❌ 被拒绝 — `"Code" is granted at tier "click" — right-click, middle-click, and clicks with modifier keys require tier "full"` |
| Finder (full tier) | ✅ 通过 — 返回 `"Clicked."` |
| 结论 | 功能正常IDE 安全限制符合预期 |
#### `middle_click` — 中键点击
| 项目 | 结果 |
|------|------|
| 状态 | ⚠️ 受 tier 限制 |
| Cursor (click tier) | ❌ 被拒绝 — 同 `right_click`,需要 full tier |
| Finder (full tier) | ✅ 通过 — 返回 `"Clicked."` |
| 结论 | 功能正常IDE 安全限制符合预期 |
#### `left_click_drag` — 拖拽
| 项目 | 结果 |
|------|------|
| 状态 | ⚠️ 受 tier 限制 |
| Cursor (click tier) | ❌ 被拒绝 — 拖拽被视为修饰键点击,需要 full tier |
| Finder (full tier) | ✅ 通过 — 返回 `"Dragged."` |
| 结论 | 功能正常IDE 安全限制符合预期 |
#### `scroll` — 滚轮滚动
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 输入 | `coordinate: [500, 500]`, `scroll_direction: "down"`, `scroll_amount: 3` |
| 返回 | `"Scrolled."` |
| 反向 | ✅ `scroll_direction: "up"` 也通过 |
---
### 4. 键盘操作
> 以下测试在 Cursor 窗口上执行tier: click— 所有键盘操作均被拒绝
#### `key` — 按键/快捷键
| 项目 | 结果 |
|------|------|
| 状态 | ⚠️ 受 tier 限制 |
| Cursor (click tier) | ❌ 被拒绝 — IDE tier 限制键盘输入 |
| Finder (full tier) | ✅ 通过 — `escape` 按键成功,返回 `"Key pressed."` |
| 结论 | 功能正常IDE 安全限制符合预期 |
#### `type` — 输入文本
| 项目 | 结果 |
|------|------|
| 状态 | ⚠️ 受 tier 限制 |
| Cursor (click tier) | ❌ 被拒绝 — IDE tier 限制文本输入 |
| Finder (full tier) | ✅ 通过 — 输入 `"hello"` 成功,返回 `"Typed 5 grapheme(s)."` |
| 结论 | 功能正常IDE 安全限制符合预期 |
#### `hold_key` — 按住按键
| 项目 | 结果 |
|------|------|
| 状态 | ⚠️ 受 tier 限制 |
| Cursor (click tier) | ❌ 被拒绝 — IDE tier 限制键盘输入 |
| Finder (full tier) | ✅ 通过 — 按住 `shift` 1 秒成功,返回 `"Key held."` |
| 结论 | 功能正常IDE 安全限制符合预期 |
---
### 5. 状态查询
#### `cursor_position` — 获取鼠标位置
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 返回 | `{"x": null, "y": null, "coordinateSpace": "image_pixels"}` |
| 说明 | 坐标为 null 是因为没有成功截图,无参考坐标系 |
---
### 6. 复合/辅助操作
#### `computer_batch` — 批量执行操作
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 行为 | 按顺序执行操作列表,遇到失败则停止后续操作 |
| 返回 | `{ completed: [...], failed: {...}, remaining: N }` |
| 特点 | 单次 API 调用执行多个操作,减少往返延迟 |
| 错误处理 | 失败的操作会中断后续操作,返回已完成和剩余数量 |
#### `wait` — 等待
| 项目 | 结果 |
|------|------|
| 状态 | ✅ 通过 |
| 输入 | `duration: 1` (秒) |
| 返回 | `"Waited 1s."` |
| 最大值 | 100 秒 |
---
## 汇总统计
| 状态 | 数量 | 工具 |
|------|------|------|
| ✅ 通过 | 10 | `request_access`, `switch_display`, `mouse_move`, `left_click`, `double_click`, `triple_click`, `scroll`, `cursor_position`, `computer_batch`, `wait` |
| ⚠️ 部分通过 | 7 | `screenshot`(执行成功但无图片返回), `right_click`, `middle_click`, `left_click_drag`, `key`, `type`, `hold_key`(均在 full tier 应用上通过IDE click tier 限制是预期行为) |
| ❌ 被拒绝 | 0 | — |
| ⏭️ 跳过 | 1 | `zoom`(依赖截图) |
---
## 已知问题
### P0: 截图无图片返回
`screenshot` 工具执行成功但未返回图片内容,导致:
- 无法获取屏幕坐标参考
- `cursor_position` 返回 null 坐标
- `zoom` 无法使用
- 所有点击操作只能盲点(无截图验证)
**可能原因**:
1. macOS 屏幕录制权限未授予
2. MCP 图片传输/编码问题
3. 截图内容被安全过滤机制过滤
**建议排查**: 检查 `系统设置 → 隐私与安全性 → 屏幕录制` 权限。
### P1: IDE 应用键盘操作受限 — ✅ 已确认功能正常
IDE 类应用Cursor、VSCode、Terminal被限制在 `click` tier无法执行
- 键盘输入(`key`, `type`, `hold_key`
- 右键/中键点击(`right_click`, `middle_click`
- 拖拽操作(`left_click_drag`
这是安全设计,防止 AI 操控 IDE 终端。**在 full tier 应用Finder、System Settings以上 6 个操作均测试通过,功能完全正常。**
---
## 权限模型说明
Computer Use MCP 采用分级权限模型:
```
┌─────────────────────────────────────────┐
│ Tier: full │
│ - 所有鼠标操作(左键、右键、中键、拖拽) │
│ - 键盘输入type, key, hold_key
│ - 适用于: 系统应用、Finder 等 │
├─────────────────────────────────────────┤
│ Tier: click │
│ - 仅纯左键点击 │
│ - 滚轮滚动 │
│ - 适用于: IDE、Terminal 等 │
├─────────────────────────────────────────┤
│ 未授权 │
│ - 所有操作被拒绝 │
│ - 需通过 request_access 申请 │
└─────────────────────────────────────────┘
```

View File

@@ -1,197 +1,136 @@
# Computer Use — macOS / Windows / Linux 跨平台实施计划
# Computer Use 用户指南
更新时间2026-04-03
参考项目:`E:\源码\claude-code-source-main\claude-code-source-main`
Computer Use 让 Claude 直接操控你的电脑——移动鼠标、点击、输入文字、截图,就像一个远程助手坐在你面前操作一样。
## 1. 现状
## 支持平台
参考项目的 Computer Use **仅支持 macOS**——从入口到底层全部写死 darwin。我们的项目在 Phase 1-3 中已经完成了:
| 平台 | 状态 | 额外配置 |
|------|------|---------|
| macOS | 可用 | 需授予辅助功能 + 屏幕录制权限 |
| Windows | 可用 | 无需额外配置 |
| Linux | 不可用 | 后端待开发 |
-`@ant/computer-use-mcp` stub 替换为完整实现12 文件)
-`@ant/computer-use-input` 拆为 dispatcher + backendsdarwin + win32
-`@ant/computer-use-swift` 拆为 dispatcher + backendsdarwin + win32
-`CHICAGO_MCP` 编译开关已开
-`src/` 层有 6 处 macOS 硬编码阻塞
## 快速开始
## 2. 阻塞点全景
1. 启动 Claude Code
### 2.1 入口层
```bash
bun run dev
```
| # | 文件:行号 | 阻塞代码 | 影响 |
|---|----------|---------|------|
| 1 | `src/main.tsx:1605` | `getPlatform() === 'macos'` | 整个 CU 初始化被跳过 |
Computer Use 默认已开启,无需额外参数。
### 2.2 加载层
2. 在对话中告诉 Claude 你想做什么,例如:
- "帮我打开系统设置"
- "截个屏看看当前桌面"
- "在 Finder 里点击那个文件"
| # | 文件:行号 | 阻塞代码 | 影响 |
|---|----------|---------|------|
| 2 | `src/utils/computerUse/swiftLoader.ts:16` | `process.platform !== 'darwin'` → throw | 截图、应用管理全部不可用 |
| 3 | `src/utils/computerUse/executor.ts:263` | `process.platform !== 'darwin'` → throw | 整个 executor 工厂函数不可用 |
3. 首次操控某个应用时,会弹出权限对话框让你确认。
### 2.3 macOS 特有依赖
4. 操作过程中随时按 **Esc**macOS或 **Ctrl+C**Windows中止。
| # | 文件:行号 | 依赖 | macOS 实现 | 需要替代方案 |
|---|----------|------|-----------|------------|
| 4 | `executor.ts:70-88` | 剪贴板 | `pbcopy`/`pbpaste` | Win: PowerShell `Get/Set-Clipboard`Linux: `xclip`/`wl-copy` |
| 5 | `drainRunLoop.ts:21` | CFRunLoop pump | `cu._drainMainRunLoop()` | 非 darwin直接执行 fn(),不需要 pump |
| 6 | `escHotkey.ts:28` | ESC 热键 | CGEventTap | 非 darwin返回 false已有 Ctrl+C fallback |
| 7 | `hostAdapter.ts:48-54` | 系统权限 | TCC accessibility + screenRecording | Win直接 grantedLinux检查 xdotool |
| 8 | `common.ts:56` | 平台标识 | `platform: 'darwin'` 硬编码 | 动态获取 |
| 9 | `executor.ts:180` | 粘贴快捷键 | `command+v` | Win/Linux`ctrl+v` |
## 权限说明
### 2.4 缺失的 Linux 后端
Computer Use 采用分级权限模型,保护你的安全:
| | macOS | Windows | Linux |
|---|-------|---------|-------|
| `computer-use-input/backends/` | ✅ darwin.ts | ✅ win32.ts | ❌ 需新建 linux.ts |
| `computer-use-swift/backends/` | ✅ darwin.ts | ✅ win32.ts | ❌ 需新建 linux.ts |
| 级别 | 能力 | 适用场景 |
|------|------|---------|
| **full** | 所有操作:鼠标点击(左/右/中键)、拖拽、键盘输入、组合键 | 系统设置、Finder 等系统应用 |
| **click** | 仅左键点击和滚轮滚动 | IDEVS Code、Cursor、终端 |
| 未授权 | 所有操作被拒绝 | 需要通过 `request_access` 申请 |
## 3. 每个平台的能力依赖
IDE 类应用默认只有 click 权限,这是安全设计——防止 AI 在你的终端或编辑器中执行危险操作。如需完整控制,可以在权限对话框中手动提升。
### 3.1 computer-use-input键鼠
## 可用操作
| 功能 | macOS | Windows | Linux |
|------|-------|---------|-------|
| 鼠标移动 | CGEvent JXA | SetCursorPos P/Invoke | xdotool mousemove |
| 鼠标点击 | CGEvent JXA | SendInput P/Invoke | xdotool click |
| 鼠标滚轮 | CGEvent JXA | SendInput MOUSEEVENTF_WHEEL | xdotool scroll |
| 键盘按键 | System Events osascript | keybd_event P/Invoke | xdotool key |
| 组合键 | System Events osascript | keybd_event 组合 | xdotool key combo |
| 文本输入 | System Events keystroke | SendKeys.SendWait | xdotool type |
| 前台应用 | System Events osascript | GetForegroundWindow P/Invoke | xdotool getactivewindow + /proc |
| 工具依赖 | osascript内置 | powershell内置 | xdotool需安装 |
### 鼠标
### 3.2 computer-use-swift截图 + 应用管理)
| 操作 | 说明 |
|------|------|
| 移动鼠标 | 移动到指定坐标 |
| 左键点击 | 单击、双击、三击 |
| 右键点击 | 需要 full 权限 |
| 中键点击 | 需要 full 权限 |
| 拖拽 | 从 A 点拖到 B 点,需要 full 权限 |
| 滚轮 | 向上或向下滚动 |
| 功能 | macOS | Windows | Linux |
|------|-------|---------|-------|
| 全屏截图 | screencapture | CopyFromScreen | gnome-screenshot / scrot / grim |
| 区域截图 | screencapture -R | CopyFromScreen(rect) | gnome-screenshot -a / scrot -a / grim -g |
| 显示器列表 | CGGetActiveDisplayList JXA | Screen.AllScreens | xrandr --query |
| 运行中应用 | System Events JXA | Get-Process | wmctrl -l / ps |
| 打开应用 | osascript activate | Start-Process | xdg-open / gtk-launch |
| 隐藏/显示 | System Events visibility | ShowWindow/SetForegroundWindow | wmctrl -c / xdotool |
| 工具依赖 | screencapture + osascript | powershell | xdotool + scrot/grim + wmctrl |
### 键盘
### 3.3 executor 层
| 操作 | 说明 |
|------|------|
| 按键 | 单个按键或组合键(如 Ctrl+C |
| 输入文字 | 逐字符输入文本,需要 full 权限 |
| 长按 | 按住某个键一段时间,需要 full 权限 |
| 功能 | macOS | Windows | Linux |
|------|-------|---------|-------|
| drainRunLoop | CFRunLoop pump | 不需要 | 不需要 |
| ESC 热键 | CGEventTap | 跳过Ctrl+C fallback | 跳过Ctrl+C fallback |
| 剪贴板读 | pbpaste | `powershell Get-Clipboard` | xclip -o / wl-paste |
| 剪贴板写 | pbcopy | `powershell Set-Clipboard` | xclip / wl-copy |
| 粘贴快捷键 | command+v | ctrl+v | ctrl+v |
| 终端检测 | __CFBundleIdentifier | WT_SESSION / TERM_PROGRAM | TERM_PROGRAM |
| 系统权限 | TCC check | 直接 granted | 检查 xdotool 安装 |
### 屏幕
## 4. 执行步骤
| 操作 | 说明 |
|------|------|
| 截图 | 截取当前屏幕 |
| 切换显示器 | 多显示器环境下切换目标屏幕 |
| 缩放 | 放大屏幕某个区域 |
### Phase 1已完成 ✅
### 其他
- [x] `@ant/computer-use-mcp` stub → 完整实现
- [x] `@ant/computer-use-input` dispatcher + darwin/win32 backends
- [x] `@ant/computer-use-swift` dispatcher + darwin/win32 backends
- [x] `CHICAGO_MCP` 编译开关
| 操作 | 说明 |
|------|------|
| 获取鼠标位置 | 查询当前鼠标坐标 |
| 批量操作 | 一次执行多个操作,减少等待 |
| 等待 | 暂停指定秒数(最长 100 秒) |
### Phase 2移除 6 处 macOS 硬编码(解锁 macOS + Windows
## macOS 权限配置
**改动原则macOS 代码路径不变,只在每处 darwin 守卫后加 win32/linux 分支。**
首次使用前,需要授予两项系统权限。缺少任一项都会导致功能异常(见下方说明)。
| 步骤 | 文件 | 改动 |
|------|------|------|
| 2.1 | `src/main.tsx:1605` | `getPlatform() === 'macos'` → 去掉平台限制,或改为 `!== 'unknown'` |
| 2.2 | `src/utils/computerUse/swiftLoader.ts:16-18` | 移除 `process.platform !== 'darwin'` throw。`@ant/computer-use-swift/index.ts` 已有跨平台 dispatch |
| 2.3 | `src/utils/computerUse/executor.ts:263-267` | 移除 `process.platform !== 'darwin'` throw。改为检查 input/swift isSupported |
| 2.4 | `src/utils/computerUse/executor.ts:70-88` | 剪贴板函数按平台分发darwin→pbcopy/pbpastewin32→PowerShell Get/Set-Clipboardlinux→xclip |
| 2.5 | `src/utils/computerUse/executor.ts:180` | `typeViaClipboard``command+v` → 非 darwin 时用 `ctrl+v` |
| 2.6 | `src/utils/computerUse/executor.ts:273` | `const cu = requireComputerUseSwift()` → 改为 `new ComputerUseAPI()`(从 package 直接实例化,不走 swiftLoader throw |
| 2.7 | `src/utils/computerUse/drainRunLoop.ts` | 开头加 `if (process.platform !== 'darwin') return fn()` |
| 2.8 | `src/utils/computerUse/escHotkey.ts` | `registerEscHotkey` 非 darwin 返回 false已有 Ctrl+C fallback |
| 2.9 | `src/utils/computerUse/hostAdapter.ts:48-54` | `ensureOsPermissions` 非 darwin 返回 `{ granted: true }` |
| 2.10 | `src/utils/computerUse/common.ts:56` | `platform: 'darwin'``platform: process.platform === 'win32' ? 'windows' : process.platform === 'linux' ? 'linux' : 'darwin'` |
| 2.11 | `src/utils/computerUse/common.ts:55` | `screenshotFiltering: 'native'` → 非 darwin 时 `'none'`Windows/Linux 截图不支持 per-app 过滤) |
| 2.12 | `src/utils/computerUse/gates.ts:13` | `enabled: false``enabled: true`(无 GrowthBook 时默认可用) |
| 2.13 | `src/utils/computerUse/gates.ts:39-43` | `hasRequiredSubscription()` → 直接返回 `true` |
### 辅助功能Accessibility
### Phase 3新增 Linux 后端
允许 Claude 控制鼠标和键盘。
| 步骤 | 文件 | 内容 |
|------|------|------|
| 3.1 | `packages/@ant/computer-use-input/src/backends/linux.ts` | xdotool 键鼠mousemove/click/key/type/getactivewindow |
| 3.2 | `packages/@ant/computer-use-swift/src/backends/linux.ts` | scrot/grim 截图 + xrandr 显示器 + wmctrl 窗口管理 |
| 3.3 | `packages/@ant/computer-use-input/src/index.ts` | dispatcher 加 `case 'linux'` |
| 3.4 | `packages/@ant/computer-use-swift/src/index.ts` | dispatcher 加 `case 'linux'` |
1. 打开 **系统设置 → 隐私与安全性 → 辅助功能**
2. 点击左下角锁图标解锁(需要管理员密码)
3. 将运行 Claude Code 的应用添加到允许列表:
- Terminal → `Terminal.app`
- iTerm → `iTerm.app`
- Cursor → `Cursor.app`
- VS Code 终端 → `Electron` 或 `Visual Studio Code.app`
4. 确保应用旁边的开关已打开
### Phase 4验证
**未授予时的现象**:鼠标移动、点击、键盘输入均无反应,工具执行成功但屏幕没有任何变化。
| 测试项 | macOS | Windows | Linux |
|--------|-------|---------|-------|
| build 成功 | ✅ | 验证 | 验证 |
| MCP 工具列表非空 | 验证 | 验证 | 验证 |
| 鼠标移动 | 验证 | ✅ 已通过 | 验证 |
| 截图 | 验证 | ✅ 已通过 | 验证 |
| 键盘输入 | 验证 | 验证 | 验证 |
| 前台窗口 | 验证 | ✅ 已通过 | 验证 |
| 剪贴板 | 验证 | 验证 | 验证 |
### 屏幕录制Screen Recording
## 5. 文件改动总览
允许 Claude 截取屏幕内容。
### 不动的文件14 个)
1. 打开 **系统设置 → 隐私与安全性 → 屏幕录制**
2. 将同一个应用添加到允许列表并开启开关
3. **需要重启该应用**才能生效(系统会提示 "xxx 需要重新打开"
`cleanup.ts``computerUseLock.ts``wrapper.tsx``toolRendering.tsx``mcpServer.ts``setup.ts``appNames.ts``inputLoader.ts``src/services/mcp/client.ts``@ant/computer-use-mcp/src/*`Phase 1 已完成)、`backends/darwin.ts`(两个包都不动)
**未授予时的现象**截图工具执行成功但返回空白图片Claude 无法看到你的屏幕,所有点击操作变成"盲点"。
### 改 src/ 的文件8 个)
### 验证权限
| 文件 | 改动量 | 风险 |
|------|--------|------|
| `main.tsx` | 1 行 | 低 |
| `swiftLoader.ts` | 2 行 | 低 |
| `executor.ts` | ~40 行(剪贴板分发 + 平台守卫 + paste 快捷键) | **中** |
| `drainRunLoop.ts` | 1 行 | 低 |
| `escHotkey.ts` | 3 行 | 低 |
| `hostAdapter.ts` | 5 行 | 低 |
| `common.ts` | 3 行 | 低 |
| `gates.ts` | 3 行 | 低 |
授予两项权限后,重启 Claude Code在对话中让 Claude 截一张图即可验证是否配置成功。如果截图内容正常显示,说明权限配置完成。
### 新增文件2 个
## Linux 依赖(暂不可用
| 文件 | 行数估算 |
|------|---------|
| `packages/@ant/computer-use-input/src/backends/linux.ts` | ~150 行 |
| `packages/@ant/computer-use-swift/src/backends/linux.ts` | ~200 行 |
Linux 后端尚未开发。完成后需要安装以下工具:
## 6. Linux 依赖工具
| 工具 | 用途 | 安装命令Ubuntu |
|------|------|-------------------|
| `xdotool` | 键鼠模拟 + 窗口管理 | `sudo apt install xdotool` |
| `scrot``gnome-screenshot` | 截图 | `sudo apt install scrot` |
| `xrandr` | 显示器信息 | 通常已预装 |
| `xclip` | 剪贴板 | `sudo apt install xclip` |
| `wmctrl` | 窗口列表/切换 | `sudo apt install wmctrl` |
Wayland 环境需要替代工具:`ydotool`(替代 xdotool`grim`(替代 scrot`wl-clipboard`(替代 xclip。初期可先只支持 X11Wayland 标记为 todo。
## 7. 执行顺序建议
```
Phase 2解锁 macOS + Windows
├── 2.1-2.3 移除 3 处硬编码 throw/skip
├── 2.4-2.5 剪贴板 + 粘贴快捷键平台分发
├── 2.6 swiftLoader → 直接实例化
├── 2.7-2.9 drainRunLoop / escHotkey / permissions 平台分支
├── 2.10-2.11 common.ts 平台标识动态化
├── 2.12-2.13 gates.ts 默认值
└── 验证 Windows
Phase 3Linux 后端)
├── 3.1 input/backends/linux.ts
├── 3.2 swift/backends/linux.ts
├── 3.3-3.4 dispatcher 加 linux case
└── 验证 Linux
Phase 4集成验证 + PR
```bash
sudo apt install xdotool scrot xclip wmctrl
```
每个 Phase 可独立验证、独立提交。Phase 2 完成后 macOS + Windows 可用Phase 3 完成后三平台全部可用。
仅支持 X11Wayland 不支持。
## 常见问题
### 截图成功但看不到图片
检查 **系统设置 → 隐私与安全性 → 屏幕录制** 是否已授权。未授权时截图工具会执行成功但返回空白内容。
### IDE 中无法输入文字或右键
这是正常行为。IDE 类应用只有 click 权限,无法执行键盘输入、右键、拖拽等操作。如需完整控制,请在系统应用(如 Finder中操作。
### 操作中途想停止
**Esc**macOS**Ctrl+C** 即可立即中止。

View File

@@ -152,7 +152,8 @@
"pages": [
"docs/features/token-budget",
"docs/features/context-collapse",
"docs/features/workflow-scripts"
"docs/features/workflow-scripts",
"docs/features/auto-dream"
]
},
"docs/features/tier3-stubs"

View File

@@ -796,6 +796,17 @@ function resolveRequestedApps(
if (!resolved) {
resolved = byLowerDisplayName.get(requested.toLowerCase());
}
// Fuzzy fallback: match requested name as substring of display name
// e.g. "Chrome" matches "Google Chrome", "Code" matches "Visual Studio Code"
if (!resolved) {
const lower = requested.toLowerCase();
for (const app of installed) {
if (app.displayName.toLowerCase().includes(lower)) {
resolved = app;
break;
}
}
}
const bundleId = resolved?.bundleId;
// When unresolved AND the requested string looks like a bundle ID, use it
// directly for tier lookup (e.g. "company.thebrowser.Browser" with Arc not

View File

@@ -159,23 +159,28 @@ export const apps: AppsAPI = {
async listInstalled() {
try {
const result = await osascript(`
tell application "System Events"
set appList to ""
repeat with appFile in (every file of folder "Applications" of startup disk whose name ends with ".app")
set appPath to POSIX path of (appFile as alias)
set appName to name of appFile
set appList to appList & appPath & "|" & appName & "\\n"
end repeat
return appList
end tell
`)
return result.split('\n').filter(Boolean).map(line => {
const [path, name] = line.split('|', 2)
const displayName = (name ?? '').replace(/\.app$/, '')
// Use Spotlight (mdfind) to enumerate .app bundles and mdls to get real bundle IDs.
// Searches /Applications, /System/Applications, and /System/Applications/Utilities
// so that system apps (Terminal, Chess, etc.) and core services (Finder) are found.
const proc = Bun.spawn([
'bash', '-c',
`for dir in /Applications /System/Applications /System/Applications/Utilities /System/Library/CoreServices; do
mdfind 'kMDItemContentType == "com.apple.application-bundle"' -onlyin "$dir" 2>/dev/null
done | sort -u | while read -r appPath; do
bundleId=$(mdls -raw -name kMDItemCFBundleIdentifier "$appPath" 2>/dev/null)
if [ -n "$bundleId" ] && [ "$bundleId" != "(null)" ]; then
displayName=$(basename "$appPath" .app)
echo "$bundleId|$displayName|$appPath"
fi
done`,
], { stdout: 'pipe', stderr: 'pipe' })
const text = await new Response(proc.stdout).text()
await proc.exited
return text.split('\n').filter(Boolean).map(line => {
const [bundleId, displayName, path] = line.split('|', 3)
return {
bundleId: `com.app.${displayName.toLowerCase().replace(/\s+/g, '-')}`,
displayName,
bundleId: bundleId ?? '',
displayName: displayName ?? '',
path: path ?? '',
}
})

View File

@@ -80,6 +80,12 @@ export class ComputerUseAPI {
async captureRegion() { throw new Error('computer-use-swift: no backend for this platform') },
}
hotkey = (backend as any)?.hotkey ?? {
registerEscape(_cb: () => void): boolean { return false },
unregister() {},
notifyExpectedEscape() {},
}
async resolvePrepareCapture(
allowedBundleIds: string[],
_surrogateHost: string,

View File

@@ -6,9 +6,16 @@
*
* Idempotent — skips download if the binary already exists.
* Use --force to re-download.
*
* Environment:
* - HTTPS_PROXY / HTTP_PROXY — when set, download uses `undici` + EnvHttpProxyAgent.
* - RIPGREP_DOWNLOAD_BASE — override release URL prefix, e.g. mirror:
* `https://ghproxy.net/https://github.com/microsoft/ripgrep-prebuilt/releases/download/v15.0.1`
*/
import { existsSync, mkdirSync, renameSync, rmSync, statSync } from 'fs'
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from 'fs'
import { setDefaultResultOrder } from 'node:dns'
import { tmpdir } from 'os'
import { chmodSync } from 'fs'
import { spawnSync } from 'child_process'
import * as path from 'path'
@@ -17,8 +24,16 @@ import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// Prefer IPv4 first — Bun on Windows sometimes fails GitHub over broken IPv6 paths.
try {
setDefaultResultOrder('ipv4first')
} catch {
/* ignore */
}
const RG_VERSION = '15.0.1'
const BASE_URL = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
const DEFAULT_RELEASE_BASE = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
const RELEASE_BASE = (process.env.RIPGREP_DOWNLOAD_BASE ?? DEFAULT_RELEASE_BASE).replace(/\/$/, '')
// --- Platform mapping ---
@@ -97,10 +112,94 @@ function getBinaryPath(): string {
// --- Download & extract ---
function proxyEnvSet(): boolean {
const v = (s: string | undefined) => (s ?? '').trim()
return !!(
v(process.env.HTTPS_PROXY) ||
v(process.env.HTTP_PROXY) ||
v(process.env.ALL_PROXY) ||
v(process.env.https_proxy) ||
v(process.env.http_proxy)
)
}
async function fetchRelease(url: string): Promise<Response> {
if (proxyEnvSet()) {
const { EnvHttpProxyAgent, fetch: undiciFetch } = await import('undici')
return (await undiciFetch(url, {
redirect: 'follow',
dispatcher: new EnvHttpProxyAgent(),
})) as unknown as Response
}
return await fetch(url, { redirect: 'follow' })
}
function tryPowerShellDownload(url: string, dest: string): boolean {
const u = url.replace(/'/g, "''")
const d = dest.replace(/'/g, "''")
const cmd = `Invoke-WebRequest -Uri '${u}' -OutFile '${d}' -UseBasicParsing`
const result = spawnSync(
'powershell.exe',
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', cmd],
{ stdio: 'pipe', windowsHide: true },
)
return result.status === 0 && existsSync(dest) && statSync(dest).size > 0
}
function tryCurlDownload(url: string, dest: string): boolean {
const curl = process.platform === 'win32' ? 'curl.exe' : 'curl'
const result = spawnSync(curl, ['-fsSL', '-L', '--fail', '-o', dest, url], {
stdio: 'pipe',
windowsHide: true,
})
return result.status === 0 && existsSync(dest) && statSync(dest).size > 0
}
/** Bun `fetch` on Windows can fail while browser / WinINET still works — use subprocess fallbacks. */
async function downloadUrlToBuffer(url: string): Promise<Buffer> {
const response = await fetchRelease(url)
if (!response.ok) {
throw new Error(`Download failed: ${response.status} ${response.statusText}`)
}
return Buffer.from(await response.arrayBuffer())
}
async function downloadUrlToBufferWithFallback(url: string): Promise<Buffer> {
let firstError: unknown
try {
return await downloadUrlToBuffer(url)
} catch (e) {
firstError = e
}
const tmpRoot = path.join(tmpdir(), `ripgrep-dl-${process.pid}-${Date.now()}`)
const tmpFile = path.join(tmpRoot, 'archive')
mkdirSync(tmpRoot, { recursive: true })
try {
if (process.platform === 'win32' && tryPowerShellDownload(url, tmpFile)) {
return readFileSync(tmpFile)
}
if (tryCurlDownload(url, tmpFile)) {
return readFileSync(tmpFile)
}
} finally {
rmSync(tmpRoot, { recursive: true, force: true })
}
throw firstError
}
function findZipEntryKey(files: Record<string, Uint8Array>, want: string): string | undefined {
return Object.keys(files).find(k => {
const norm = k.replace(/\\/g, '/')
return norm === want || norm.endsWith(`/${want}`)
})
}
async function downloadAndExtract(): Promise<void> {
const { target, ext } = getPlatformMapping()
const assetName = `ripgrep-v${RG_VERSION}-${target}.${ext}`
const downloadUrl = `${BASE_URL}/${assetName}`
const downloadUrl = `${RELEASE_BASE}/${assetName}`
const binaryPath = getBinaryPath()
const binaryDir = path.dirname(binaryPath)
@@ -118,66 +217,111 @@ async function downloadAndExtract(): Promise<void> {
console.log(`[ripgrep] Downloading v${RG_VERSION} for ${target}...`)
console.log(`[ripgrep] URL: ${downloadUrl}`)
// Prepare temp directory
const tmpDir = path.join(binaryDir, '.tmp-download')
rmSync(tmpDir, { recursive: true, force: true })
mkdirSync(tmpDir, { recursive: true })
const extractedBinary = process.platform === 'win32' ? 'rg.exe' : 'rg'
const { writeFileSync } = await import('fs')
try {
const archivePath = path.join(tmpDir, assetName)
// Download
const response = await fetch(downloadUrl, { redirect: 'follow' })
if (!response.ok) {
throw new Error(`Download failed: ${response.status} ${response.statusText}`)
}
const buffer = Buffer.from(await response.arrayBuffer())
const { writeFileSync } = await import('fs')
writeFileSync(archivePath, buffer)
const buffer = await downloadUrlToBufferWithFallback(downloadUrl)
console.log(`[ripgrep] Downloaded ${Math.round(buffer.length / 1024)} KB`)
// Extract
mkdirSync(binaryDir, { recursive: true })
if (ext === 'tar.gz') {
const result = spawnSync('tar', ['xzf', archivePath, '-C', tmpDir], {
stdio: 'pipe',
})
if (result.status !== 0) {
throw new Error(`tar extract failed: ${result.stderr?.toString()}`)
const tmpDir = path.join(binaryDir, '.tmp-download')
rmSync(tmpDir, { recursive: true, force: true })
mkdirSync(tmpDir, { recursive: true })
try {
const archivePath = path.join(tmpDir, assetName)
writeFileSync(archivePath, buffer)
const result = spawnSync('tar', ['xzf', archivePath, '-C', tmpDir], {
stdio: 'pipe',
})
if (result.status !== 0) {
throw new Error(`tar extract failed: ${result.stderr?.toString()}`)
}
const srcBinary = path.join(tmpDir, extractedBinary)
if (!existsSync(srcBinary)) {
throw new Error(`Binary not found at expected path: ${srcBinary}`)
}
renameSync(srcBinary, binaryPath)
} finally {
rmSync(tmpDir, { recursive: true, force: true })
}
} else {
// .zip
const result = spawnSync('unzip', ['-o', archivePath, '-d', tmpDir], {
stdio: 'pipe',
})
if (result.status !== 0) {
throw new Error(`unzip failed: ${result.stderr?.toString()}`)
let fflateError: unknown
try {
const { unzipSync } = await import('fflate')
const unzipped = unzipSync(new Uint8Array(buffer))
const key = findZipEntryKey(unzipped, extractedBinary)
if (!key) {
throw new Error(`Binary ${extractedBinary} not found in zip`)
}
writeFileSync(binaryPath, Buffer.from(unzipped[key]))
fflateError = undefined
} catch (e) {
fflateError = e
}
if (fflateError) {
// fflate failed — try PowerShell Expand-Archive on Windows, then unzip CLI
const tmpDir = path.join(binaryDir, '.tmp-download')
rmSync(tmpDir, { recursive: true, force: true })
mkdirSync(tmpDir, { recursive: true })
try {
const archivePath = path.join(tmpDir, assetName)
writeFileSync(archivePath, buffer)
let extracted = false
// On Windows, prefer PowerShell Expand-Archive
if (process.platform === 'win32') {
const psCmd = `Expand-Archive -Path '${archivePath.replace(/'/g, "''")}' -DestinationPath '${tmpDir.replace(/'/g, "''")}' -Force`
const psResult = spawnSync(
'powershell.exe',
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', psCmd],
{ stdio: 'pipe', windowsHide: true },
)
if (psResult.status === 0) {
extracted = true
} else {
const psErr = psResult.stderr?.toString().trim() || 'unknown error'
console.log(`[ripgrep] PowerShell Expand-Archive failed: ${psErr}`)
}
}
// Fallback: unzip CLI (Git for Windows, MSYS2, or Unix)
if (!extracted) {
const result = spawnSync('unzip', ['-o', archivePath, '-d', tmpDir], {
stdio: 'pipe',
})
if (result.status !== 0) {
const unzipErr = result.stderr?.toString().trim() || 'command not found'
const fflateMsg = fflateError instanceof Error ? fflateError.message : String(fflateError)
throw new Error(`zip extraction failed (fflate: ${fflateMsg}; unzip: ${unzipErr})`)
}
}
const srcBinary = path.join(tmpDir, extractedBinary)
if (!existsSync(srcBinary)) {
throw new Error(`Binary not found at expected path: ${srcBinary}`)
}
renameSync(srcBinary, binaryPath)
} finally {
rmSync(tmpDir, { recursive: true, force: true })
}
}
}
// Find the rg binary in the extracted directory
// microsoft/ripgrep-prebuilt archives extract flat: ./rg (no subdirectory)
const extractedBinary = process.platform === 'win32' ? 'rg.exe' : 'rg'
const srcBinary = path.join(tmpDir, extractedBinary)
if (!existsSync(srcBinary)) {
throw new Error(`Binary not found at expected path: ${srcBinary}`)
}
// Move to final location
renameSync(srcBinary, binaryPath)
// Make executable (non-Windows)
if (process.platform !== 'win32') {
chmodSync(binaryPath, 0o755)
}
console.log(`[ripgrep] Installed to ${binaryPath}`)
} finally {
// Cleanup temp directory
rmSync(tmpDir, { recursive: true, force: true })
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
const hint =
'Check network or set HTTPS_PROXY. If GitHub is blocked, set RIPGREP_DOWNLOAD_BASE to a mirror (see script header).'
throw new Error(`${msg} ${hint}`)
}
}

View File

@@ -1,3 +1,7 @@
// Auto-generated type stub — replace with real implementation
export type DreamTask = any;
export type DreamTaskState = any;
// Type re-exports for DreamTask — bridges the component tree to the task registry.
// The real implementation lives in src/tasks/DreamTask/DreamTask.ts.
// Note: Currently unused — BackgroundTasksDialog.tsx imports directly from
// src/tasks/DreamTask/DreamTask.js. Kept for decompilation completeness.
export type { DreamTaskState, DreamPhase, DreamTurn } from '../../../../../tasks/DreamTask/DreamTask.js'
export { isDreamTask, registerDreamTask, addDreamTurn, completeDreamTask, failDreamTask, DreamTask } from '../../../../../tasks/DreamTask/DreamTask.js'

View File

@@ -2,7 +2,8 @@ import { isInBundledMode } from 'src/utils/bundledMode.js';
import { getCurrentInstallationType } from 'src/utils/doctorDiagnostic.js';
import { isEnvTruthy } from 'src/utils/envUtils.js';
import { useStartupNotification } from './useStartupNotification.js';
const NPM_DEPRECATION_MESSAGE = 'Claude Code has switched from npm to native installer. Run `claude install` or see https://docs.anthropic.com/en/docs/claude-code/getting-started for more options.';
const NPM_DEPRECATION_MESSAGE = '';
// const NPM_DEPRECATION_MESSAGE = 'Claude Code has switched from npm to native installer. Run `claude install` or see https://docs.anthropic.com/en/docs/claude-code/getting-started for more options.';
export function useNpmDeprecationNotification() {
useStartupNotification(_temp);
}

View File

@@ -11,6 +11,8 @@ import { normalizeMessagesForAPI } from '../../../utils/messages.js'
import { toolToAPISchema } from '../../../utils/api.js'
import { getEmptyToolPermissionContext } from '../../../Tool.js'
import { logForDebugging } from '../../../utils/debug.js'
import { addToTotalSessionCost } from '../../../cost-tracker.js'
import { calculateUSDCost } from '../../../utils/modelCost.js'
import type { Options } from '../claude.js'
import { randomUUID } from 'crypto'
import {
@@ -189,6 +191,12 @@ export async function* queryModelOpenAI(
break
}
// Track cost and token usage (matching the Anthropic path in claude.ts)
if (event.type === 'message_stop' && usage.input_tokens + usage.output_tokens > 0) {
const costUSD = calculateUSDCost(openaiModel, usage as any)
addToTotalSessionCost(costUSD, usage as any, options.model)
}
// Also yield as StreamEvent for real-time display (matching Anthropic path)
yield {
type: 'stream_event',

View File

@@ -0,0 +1,44 @@
// Manual /dream skill — runs the memory consolidation prompt interactively.
// Extracted from the KAIROS feature gate so it's available unconditionally
// whenever auto-memory is enabled.
import { getAutoMemPath, isAutoMemoryEnabled } from '../../memdir/paths.js'
import { buildConsolidationPrompt } from '../../services/autoDream/consolidationPrompt.js'
import { recordConsolidation } from '../../services/autoDream/consolidationLock.js'
import { getOriginalCwd } from '../../bootstrap/state.js'
import { getProjectDir } from '../../utils/sessionStorage.js'
import { registerBundledSkill } from '../bundledSkills.js'
const DREAM_PROMPT_PREFIX = `# Dream: Memory Consolidation (manual run)
You are performing a manual dream — a reflective pass over your memory files. Unlike the automatic background dream, this run has full tool permissions and the user is watching. Synthesize what you've learned recently into durable, well-organized memories so that future sessions can orient quickly.
`
export function registerDreamSkill(): void {
registerBundledSkill({
name: 'dream',
description:
'Manually trigger memory consolidation — review, organize, and prune your auto-memory files.',
whenToUse:
'Use when the user says /dream or wants to manually consolidate memories, organize memory files, or clean up stale entries.',
userInvocable: true,
isEnabled: () => isAutoMemoryEnabled(),
async getPromptForCommand(args) {
const memoryRoot = getAutoMemPath()
const transcriptDir = getProjectDir(getOriginalCwd())
// Stamp the consolidation lock optimistically (same as the KAIROS path).
await recordConsolidation()
const basePrompt = buildConsolidationPrompt(memoryRoot, transcriptDir, '')
let prompt = DREAM_PROMPT_PREFIX + basePrompt
if (args) {
prompt += `\n\n## Additional context from user\n\n${args}`
}
return [{ type: 'text', text: prompt }]
},
})
}

View File

@@ -10,6 +10,7 @@ import { registerSimplifySkill } from './simplify.js'
import { registerSkillifySkill } from './skillify.js'
import { registerStuckSkill } from './stuck.js'
import { registerLoopSkill } from './loop.js'
import { registerDreamSkill } from './dream.js'
import { registerUpdateConfigSkill } from './updateConfig.js'
import { registerVerifySkill } from './verify.js'
@@ -34,12 +35,7 @@ export function initBundledSkills(): void {
registerBatchSkill()
registerStuckSkill()
registerLoopSkill()
if (feature('KAIROS') || feature('KAIROS_DREAM')) {
/* eslint-disable @typescript-eslint/no-require-imports */
const { registerDreamSkill } = require('./dream.js')
/* eslint-enable @typescript-eslint/no-require-imports */
registerDreamSkill()
}
registerDreamSkill()
if (feature('REVIEW_ARTIFACT')) {
/* eslint-disable @typescript-eslint/no-require-imports */
const { registerHunterSkill } = require('./hunter.js')

View File

@@ -46,9 +46,16 @@ export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
}),
ensureOsPermissions: async () => {
if (process.platform !== 'darwin') return { granted: true }
const cu = requireComputerUseSwift()
const accessibility = (cu as any).tcc.checkAccessibility()
const screenRecording = (cu as any).tcc.checkScreenRecording()
const cu = requireComputerUseSwift() as any
// Native .node module exposes tcc; cross-platform JS backend does not.
// When tcc is absent (JS backend on macOS), we cannot programmatically
// check TCC status — returning granted:false would create a deadlock
// (recheck also fails, user can never pass). The JS backend uses
// osascript/screencapture which trigger OS-level permission prompts
// themselves, so the OS provides the safety net instead.
if (!cu.tcc) return { granted: true }
const accessibility = cu.tcc.checkAccessibility()
const screenRecording = cu.tcc.checkScreenRecording()
return accessibility && screenRecording
? { granted: true }
: { granted: false, accessibility, screenRecording }