diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6f78829e..de4ac15c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,7 +86,7 @@ jobs: cache-all-crates: true save-if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} - - name: Sync version from tag to tauri.conf.json + - name: Sync version from tag to tauri configs shell: bash run: | VERSION="${{ github.event.inputs.version || github.ref_name }}" @@ -95,11 +95,14 @@ jobs: cd src-tauri node -e " const fs = require('fs'); - const conf = JSON.parse(fs.readFileSync('tauri.conf.json', 'utf8')); - conf.version = '$VERSION'; - fs.writeFileSync('tauri.conf.json', JSON.stringify(conf, null, 2) + '\n'); + for (const file of ['tauri.conf.json', 'tauri.conf.headless.json']) { + const conf = JSON.parse(fs.readFileSync(file, 'utf8')); + conf.version = '$VERSION'; + fs.writeFileSync(file, JSON.stringify(conf, null, 2) + '\n'); + } " echo "tauri.conf.json version is now: $(node -e "console.log(JSON.parse(require('fs').readFileSync('tauri.conf.json','utf8')).version)")" + echo "tauri.conf.headless.json version is now: $(node -e "console.log(JSON.parse(require('fs').readFileSync('tauri.conf.headless.json','utf8')).version)")" - name: Load release notes shell: bash diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1d33e77a9..704269f10 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,59 +1,46 @@ -## ProxyCast v0.86.0 +## ProxyCast v0.87.0 ### ✨ 新功能 -- **Aster 集成升级**: 升级到 aster-rust v0.17.1,带来更稳定的 Agent 运行时支持 -- **排队机制**: 新增 Agent 运行时排队系统,支持多轮对话请求的有序处理 -- **Artifact 自动预览**: 实现 Artifact 自动预览同步机制,提升内容创作体验 -- **搜索结果预览**: 新增搜索结果预览列表组件,优化 Web 搜索交互 -- **Skill 脚手架**: 新增 Skill 创建脚手架对话框,简化自定义 Skill 开发流程 -- **会话作用域存储**: 实现 Agent 会话级别的状态管理机制 +- **浏览器协助运行时**:新增 `browser-runtime` crate、Tauri 命令与调试页,支持在 Agent 会话内拉起浏览器协助能力 +- **Browser Assist 产物渲染**:新增 Browser Assist Artifact 渲染器,可在 General 会话中直接承接浏览器协助状态与操作 +- **画布工作台布局**:新增 `CanvasWorkbenchLayout`,支持预览 / 文件 / 差异 / 工作台联动,以及分栏与堆叠两种布局模式 +- **技能显式选择器**:新增 `SkillSelector` 与相关 harness skill 聚合逻辑,提升技能发现、点选与触发体验 +- **新任务入口模式**:新增 `new-task` 入口、空状态页与会话恢复控制,梳理 Claw / 新任务两类进入路径 ### 🔧 优化与重构 -- **Request Tool Policy 重构**: 大幅重构请求工具策略模块(+1176 行),提升 Web 搜索预调用的可靠性 -- **Skill 服务增强**: 重构 Skill 加载器和匹配器,优化 Skill 发现和执行流程(+1064 行) -- **API Key Provider 优化**: 增强 API Key 提供商服务,改进凭证池管理(+433 行) -- **Skill Model 完善**: 新增 Skill 模型定义,规范化 Skill 元数据管理(+345 行) -- **Aster State 扩展**: 扩展 Agent 状态管理,支持更复杂的运行时场景(+367 行) -- **数据库 Schema 更新**: 新增 agent_runtime_queue 表,支持排队机制持久化 +- **General Chat 主流程重构**:重写 Agent Chat 页的大量 General 场景逻辑,统一画布、Artifact、Workbench 与浏览器协助状态 +- **Webview / Browser Runtime 协调增强**:扩展 `webview_cmd`、窗口服务与前端 API,补齐浏览器运行时会话、窗口与控制链路 +- **Settings v2 大规模重排**:重构外观、快捷键、频道、Chrome Relay、环境、安全性能、账户统计等设置页 +- **项目与记忆工作区升级**:重构内容列表、内容编辑器、项目选择器及多类 Memory 面板,统一信息架构与交互密度 +- **导航与侧栏收敛**:调整应用侧栏、任务侧栏、资源与工作区导航,移除部分旧页面与冗余入口 ### 🐛 修复 -- **Clippy 警告修复**: 修复多个 Rust clippy 警告,提升代码质量 - - 简化 `let...else` 为 `?` 操作符 - - 合并连续的字符串替换操作 - - 优化字符匹配模式 -- **类型安全改进**: 修复前端类型定义,增强 TypeScript 类型检查 - -### 📦 依赖更新 - -- 升级 aster-rust 到 v0.17.1 -- 更新相关 Rust 依赖包版本 +- **前端 Hooks 依赖修复**:补齐 `useCallback` 依赖,消除 `npm run lint` 中的 `react-hooks/exhaustive-deps` 警告 +- **Rust Clippy 清理**:移除浏览器运行时客户端中的冗余类型转换,保持 `cargo clippy` 输出干净 +- **日志测试并发污染修复**:将日志诊断测试切到独立临时目录,避免并行测试互相删除 `raw_response` 工件 +- **Workspace 错误态兜底**:补强任务侧栏与相关页面的 workspace 异常显示与恢复路径 ### 🧪 测试 -- 新增多个组件单元测试 - - Modal 组件测试 - - SearchResultPreviewList 测试 - - ArtifactRenderer UI 测试 - - SkillScaffoldDialog 测试 - - useArtifactAutoPreviewSync 测试 - - useArtifactDisplayState 测试 - - searchQueryGrouping 测试 +- 新增 Browser Assist、Canvas Workbench、Skill Selector、General Resource Sync、Harness Skills 等前端测试 +- 补充项目页、设置页、统计页、内存面板等多处 UI / 交互测试 +- 发布前已执行:`cargo fmt --all`、`cargo test`、`cargo clippy`、`npm run lint` ### 📝 文档 -- 新增设计语言文档 (design-language.md) -- 更新 AGENTS.md,补充跨平台兼容约束和 UI 指导 -- 完善 aiprompts 文档索引 +- 补充 Agent Chat / Components 文档说明 +- 新增 `docs/research/` 研究资料目录 +- 更新发布流程相关说明,覆盖 headless Tauri 配置版本同步 ### 🛠️ 开发体验 -- 新增本地 aster-rust 覆盖脚本 (setup-local-aster-override.mjs) -- 改进 legacy surfaces 报告脚本 -- 优化开发工具链配置 +- **版本一致性检查增强**:`check-app-version-consistency.mjs` 现覆盖 `tauri.conf.headless.json` +- **Release Workflow 补强**:GitHub Release 工作流现同步标准与 headless 两份 Tauri 配置版本 +- **跨端发布一致性提升**:统一 `package.json`、Cargo workspace、Tauri 配置与 release notes 的版本入口 --- -**完整变更**: v0.85.0...v0.86.0 +**完整变更**: v0.86.0...v0.87.0 diff --git a/docs/research/next-browser-reference.md b/docs/research/next-browser-reference.md new file mode 100644 index 000000000..b4552b8e2 --- /dev/null +++ b/docs/research/next-browser-reference.md @@ -0,0 +1,508 @@ +# next-browser 项目参考分析 + +> 来源: `/Users/coso/Documents/dev/ai/skills/next-browser` +> 项目: @vercel/next-browser - Vercel 官方的浏览器自动化工具 + +## 项目概述 + +`next-browser` 是 Vercel 开发的一个 **CLI 工具**,为 AI Agent 提供对 React DevTools 和 Next.js dev server 的编程访问。它将 GUI 操作(组件树、props、hooks、PPR shells、错误)转换为 **结构化文本输出的 shell 命令**。 + +### 核心理念 +- **为 AI Agent 设计** - LLM 无法读取 DevTools 面板,但可以运行 `next-browser tree` 并解析输出 +- **无状态命令** - 每个命令都是对长期运行的浏览器守护进程的一次性调用 +- **结构化输出** - 所有输出都是可解析的文本/JSON,便于 Agent 处理 + +--- + +## 值得 Proxycast 参考的设计 + +### 1. 守护进程 + 客户端架构 ⭐⭐⭐ + +**设计模式:** +``` +CLI 命令 → Unix Socket → 守护进程 → Playwright Browser +``` + +**实现细节:** +```typescript +// daemon.ts - 守护进程 +const server = createServer((socket) => { + let buffer = ""; + socket.on("data", (chunk) => { + buffer += chunk; + let newline: number; + while ((newline = buffer.indexOf("\n")) >= 0) { + const line = buffer.slice(0, newline); + buffer = buffer.slice(newline + 1); + if (line) dispatch(line, socket); + } + }); +}); + +server.listen(socketPath); // Unix socket + +async function dispatch(line: string, socket: Socket) { + const cmd = JSON.parse(line); + const result = await run(cmd).catch((err) => ({ ok: false, error: cleanError(err) })); + socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n"); +} +``` + +**Proxycast 应用:** +- ✅ **替代 Tauri event 的高吞吐方案** - 当事件推送超过 100/s 时,切换到本地 Unix Socket +- ✅ **守护进程管理浏览器实例** - 避免每次命令都启动新浏览器 +- ✅ **命令-响应模式** - 清晰的请求/响应边界,便于错误处理 + +--- + +### 2. Playwright + React DevTools 集成 ⭐⭐⭐ + +**核心技术:** +```typescript +// browser.ts +const extensionPath = resolve(import.meta.dirname, "../extensions/react-devtools-chrome"); + +// Pre-read the hook script +const installHook = readFileSync( + join(extensionPath, "build", "installHook.js"), + "utf-8", +); + +async function launch() { + const context = await chromium.launchPersistentContext(profileDirPath, { + headless: false, + args: [ + `--disable-extensions-except=${extensionPath}`, + `--load-extension=${extensionPath}`, + "--auto-open-devtools-for-tabs", + ], + }); + + // Pre-inject hook to win the race against extension + await context.addInitScript(installHook); + + return context; +} +``` + +**关键亮点:** +1. **预加载 React DevTools 扩展** - 通过 `--load-extension` 加载 Chrome 扩展 +2. **预注入 Hook** - 使用 `addInitScript` 在页面加载前注入 React DevTools hook +3. **自动打开 DevTools** - `--auto-open-devtools-for-tabs` 确保扩展激活 + +**Proxycast 应用:** +- ✅ **扩展 CDP 能力** - 不仅仅是原始 CDP,还可以加载自定义扩展 +- ✅ **预注入脚本** - 在页面加载前注入监控/调试脚本 +- ✅ **持久化上下文** - 使用 `launchPersistentContext` 保持会话状态 + +--- + +### 3. 组件树提取 ⭐⭐ + +**实现原理:** +```typescript +// tree.ts +export async function tree(page: Page) { + // 通过 React DevTools hook 获取组件树 + const operations = await page.evaluate(() => { + return window.__REACT_DEVTOOLS_GLOBAL_HOOK__.flushInitialOperations(); + }); + + // 解析 operations 构建组件树 + const tree = parseOperations(operations); + return tree; +} + +export async function inspect(page: Page, nodeId: number) { + // 获取组件的 props/hooks/state + const data = await page.evaluate((id) => { + return window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inspectElement(id); + }, nodeId); + + return { + props: data.props, + hooks: data.hooks, + state: data.state, + source: data.source, + }; +} +``` + +**Proxycast 应用:** +- ✅ **深度调试能力** - 不仅看到 DOM,还能看到 React 组件结构 +- ✅ **状态检查** - 检查组件的 props/hooks/state +- ✅ **源码定位** - 通过 source map 定位到原始源码位置 + +--- + +### 4. PPR (Partial Prerendering) 锁定机制 ⭐⭐⭐ + +**核心概念:** +```typescript +// browser.ts +let release: (() => void) | null = null; +let settled: Promise | null = null; + +export async function lock() { + if (release) return; // Already locked + + // Use @next/playwright's instant() to set cookie + const { promise, resolve } = Promise.withResolvers(); + release = resolve; + settled = promise; + + await instant(page!, async () => { + await settled; // Block until unlock() + }); +} + +export async function unlock() { + if (!release) return; + + // Capture locked state + const lockedSnapshot = await captureSuspenseBoundaries(); + + // Release the lock + release(); + release = null; + settled = null; + + // Wait for boundaries to settle + await page!.waitForTimeout(500); + + // Capture unlocked state + const unlockedSnapshot = await captureSuspenseBoundaries(); + + // Analyze the diff + return analyzePPRShell(lockedSnapshot, unlockedSnapshot); +} +``` + +**工作原理:** +1. **Lock** - 设置 `next-instant-navigation-testing=1` cookie,阻止动态数据加载 +2. **Capture** - 截取 PPR shell(静态 HTML + `