Cypress cy.prompt 开发指南:本地 Bundle 调试、类型同步与云端投递机制
本文基于仓库内 guides/cy-prompt-development.md 的原始开发流程编写,覆盖 cy.prompt 命令在本地 cypress-services 与已部署环境下的完整调试方案、downloadPromptTypes 类型同步任务的用法,并结合 Cypress 主仓库源码深入讲解云侧 Bundle 的加载、哈希校验、热重载与错误上报等底层机制。读完后你可以独立搭建 cy.prompt 本地开发环境,并理解生产二进制中相关逻辑是如何被剥离与保护的。
1. 背景:cy.prompt 代码来自 Cloud 而非主仓库
在 CYPRESS 开发文档中说明:生产环境中,支撑 cy.prompt 命令的代码是从 Cloud 获取的。也就是说,cypress monorepo 本身并不包含 prompt 的具体实现,它只包含“加载、校验、挂载、错误上报”这一层基础设施;真正的 bundle 由 cypress-services 仓库(独立 monorepo)构建后发布,运行时按 hash 从云端下载。
这个“云投递 bundle(cloud-delivered bundle)”模型在源码中体现得非常清晰:
- 生命周期管理:CyPromptLifecycleManager,负责会话创建、bundle 下载、哈希校验、本地 watch 热重载;
- 运行时管理:CyPromptManager,负责
require加载 bundle 的server/index.js并暴露initializeRoutes、addSocketListeners、connectToBrowser、reset等同步方法; - Bundle 下载:ensure_cy_prompt_bundle.ts 复用通用的签名 bundle 下载逻辑(
ensureSignedBundle),返回manifest(文件名到 sha256 的映射)和 bundle 目录; - 会话 API:post_cy_prompt_session.ts 向 Cloud 的
cy-prompt/session路由(定义于 routes.ts)发起带指数重试的 POST 请求,请求体固定携带cyPromptMountVersion: 2; - 共享类型定义:packages/types/src/cy-prompt/index.ts 声明了
CY_PROMPT_STATUSES(NOT_INITIALIZED/INITIALIZING/INITIALIZED/IN_ERROR)以及CyPromptManagerShape、CyPromptLifecycleManagerShape等跨包接口。
理解了这层架构,再来看开发文档中“本地 vs 部署”两套环境变量的设计就顺理成章了:开发者要么用已部署的 bundle(只切 Cloud 环境),要么用本地构建的 bundle(额外提供本地路径,跳过下载与校验)。
2. 本地开发模式:基于 cypress-services 仓库调试
文档给出的本地调试流程如下,完整继承自 cy-prompt-development.md:
2.1 构建并 watch 本地 bundle
- 克隆
cypress-services仓库; - 在仓库根目录执行
yarn && yarn all; - 进入
app/packages/cy-prompt目录执行yarn watch,持续产出构建产物。
2.2 设置环境变量
| 环境变量 | 取值示例 | 作用 |
|---|---|---|
CYPRESS_INTERNAL_ENV |
staging / production / development |
指定请求哪个 Cloud 部署。staging/production 用于访问对应的 cypress-services 部署环境;development 用于访问本地运行的 cypress-services |
CYPRESS_LOCAL_CY_PROMPT_PATH |
cypress-services/app/packages/cy-prompt/dist/development 目录的绝对路径 |
指向本地构建产物目录,Cypress 将直接从该目录加载 bundle 而不是从 Cloud 下载 |
CYPRESS_LOCAL_CY_PROMPT_PATH 在源码中的关键行为(均见 CyPromptLifecycleManager.ts):
- 跳过哈希校验:
createCyPromptManager中,未设置该变量时会对cyPromptUrl解析出 bundle hash,下载后计算server/index.js的 sha256 并与 manifest 中记录的期望哈希比对,不一致则抛出Invalid hash for cy prompt server script(约 L212-L227);设置该变量后则直接把路径当作 bundle 目录,cyPromptHash置为'local',manifest置为空对象(L206-L210),完全跳过下载与校验。 - watch 模式热重载:
setupWatcher(L286-L322)仅在设置该变量时启动chokidar监听<CYPRESS_LOCAL_CY_PROMPT_PATH>/server/index.js。文件变化(awaitWriteFinish: true,即等待写入完成后触发)时重新执行createCyPromptManager,实现改一行代码即重载的效果。watcher 的关闭还通过GracefulExit注册为退出步骤,保证进程退出时资源被清理。 - 监听器不一次性消费:
callRegisteredListeners回调所有已注册的 ready 监听器后,云端模式下会清空监听器列表(一次性);本地模式下保留监听器,这样每次热重载都能再次触发它们(L255-L270)。同理,registerCyPromptReadyListener在本地模式下若 manager 已就绪,会既立即调用又追加到列表(L329-L344)。 - 跳过签名验证:CyPromptManager.ts 中
setup向 bundle 注入的verifyHash回调里,只要CYPRESS_LOCAL_CY_PROMPT_PATH存在就直接返回true(L41-L43),注释明确写道:本地运行时无需校验,且“该环境变量会在二进制中被剥离”。 - 错误不进 Sentry:report_cy_prompt_error.ts(L52-L61)中,若设置了该变量(或
NODE_ENV === 'development'、CYPRESS_INTERNAL_E2E_TESTING_SELF),错误只console.error到控制台而不回传 Cloud 的/cy-prompt/errors端点——本地调试时避免污染遥测数据。
对应的单测位于 CyPromptLifecycleManager_spec.ts 与 report_cy_prompt_error_spec.ts,其中明确覆盖了 “CYPRESS_LOCAL_CY_PROMPT_PATH 设置时进入 watch 模式”“立即调用并保留监听器”“不向 Sentry 上报”等场景。
2.3 启动 Cypress 并登录 Cloud
无论使用本地还是部署版 bundle,Cypress 主仓库侧的操作相同:
- 克隆
cypress仓库; - 执行
yarn; - 执行
yarn cypress:open; - 通过 App 登录 Cloud。
注意一个容易忽略的细节:即使设置了 CYPRESS_LOCAL_CY_PROMPT_PATH,postCyPromptSession 的会话请求仍然会发起(见 L184-L186 的调用顺序),因为会话接口还承担鉴权与元数据职责,本地模式只是跳过了后续的 bundle 下载。
2.4 Cloud 环境如何被解析
CYPRESS_INTERNAL_ENV 的消费点见 get_cloud_metadata.ts:
const cloudEnv = (process.env.CYPRESS_CONFIG_ENV || process.env.CYPRESS_INTERNAL_ENV || 'production') as 'development' | 'staging' | 'production'
const cloudUrl = cloudDataSource.getCloudUrl(cloudEnv)
即优先级为 CYPRESS_CONFIG_ENV > CYPRESS_INTERNAL_ENV > 默认 production,据此选择对应的 Cloud URL。而 Gulp 任务侧在 gulpCloudDeliveredTypes.ts 首行也做了 process.env.CYPRESS_INTERNAL_ENV ??= 'production' 的兜底。
3. 类型同步:downloadPromptTypes 任务
文档 “Types” 一节指出:prompt bundle 会为 app、driver、server 三个接口提供类型定义,需要将其纳入主代码库。同步命令:
yarn gulp downloadPromptTypes
或指向本地 cypress-services 仓库的产物目录:
CYPRESS_LOCAL_CY_PROMPT_PATH=<path-to-cypress-services/app/packages/cy-prompt/dist/development-directory> yarn gulp downloadPromptTypes
任务实现位于 scripts/gulp/tasks/gulpCloudDeliveredTypes.ts,并注册于 gulpfile.ts。其执行逻辑为:
- 若未设置
CYPRESS_LOCAL_CY_PROMPT_PATH:调用postCyPromptSession({ projectId: 'ypt4pf' })获取 bundle URL,从 URL 末段提取 hash 作为目录名(getBundlePath,L24-L32,存放于os.tmpdir()/cypress/cy-prompt/<hash>),再通过ensureCyPromptBundle下载并校验 bundle; - 若已设置该变量:直接以本地路径为源,跳过会话与下载;
- 按映射表复制类型文件。prompt 的三处映射(
createPromptTypeMappings,L83-L98)为:
| bundle 内源文件 | 主仓库目标文件 |
|---|---|
app/types.ts |
packages/app/src/prompt/prompt-app-types.ts |
driver/types.ts |
packages/driver/src/cy/commands/prompt/prompt-driver-types.ts |
server/types.ts |
packages/types/src/cy-prompt/cy-prompt-server-types.ts |
这也解释了为什么 packages/types/src/cy-prompt/cy-prompt-server-types.ts 里的 CyPromptServerShape 等方法签名在 monorepo 中看似“凭空出现”——它是云侧构建产物的同步副本。同文件中还有结构完全对称的 downloadStudioTypes 任务(对应 CYPRESS_LOCAL_STUDIO_PATH),说明 prompt 与 studio 共用同一套 cloud-delivered types 基础设施。
4. 生产二进制中的剥离保护
本地开发能力必须不能泄漏进分发给用户的二进制。scripts/binary/binary-sources.js 的 getCyPromptFileSource(L122-L130)在打包阶段执行:
if (!fileContents.includes('process.env.CYPRESS_LOCAL_CY_PROMPT_PATH')) {
throw new Error(`Expected to find CYPRESS_LOCAL_CY_PROMPT_PATH in cy prompt file`)
}
return fileContents.replaceAll('process.env.CYPRESS_LOCAL_CY_PROMPT_PATH', 'undefined')
随后 validateCyPromptFile(L148-L154)复核产物中确实不再包含该环境变量。这意味着发布二进制中所有 process.env.CYPRESS_LOCAL_CY_PROMPT_PATH 判断恒为 falsy:哈希校验强制执行、watch 永不启用、错误始终上报 Cloud。这也与 CyPromptManager.ts 中“该环境变量会在二进制中被剥离”的注释相互印证。
5. 测试策略
文档 “Testing” 一节说明了两处测试的分工:
- cypress monorepo 侧(支撑
cy.prompt的云侧基础设施代码):按仓库统一的 unit / integration / e2e 分层测试,具体规范见 CONTRIBUTING.md。典型用例即前文提到的 CyPromptLifecycleManager_spec.ts(watch 模式、监听器生命周期、本地路径分支)与 report_cy_prompt_error_spec.ts(本地模式只打印不上报)。 - cypress-services monorepo 侧(prompt 业务实现本身):单元测试与该仓库内代码同目录存放,随 bundle 工程独立维护。
对贡献者而言的实践含义是:改动主仓库中 cy-prompt 加载/校验逻辑后,应运行 packages/server 下对应的 vitest 单测并检查快照;改动 prompt 行为本身则需在 cypress-services 仓库走其自身的测试与发布流程,再通过第 2 节的 yarn watch + CYPRESS_LOCAL_CY_PROMPT_PATH 链路在主仓库中端到端验证。
6. 小结
- 本地调试
cy.prompt的核心是三件套:cypress-services侧yarn watch持续构建、CYPRESS_LOCAL_CY_PROMPT_PATH指向dist/development、CYPRESS_INTERNAL_ENV选择目标 Cloud 环境(development指向本地cypress-services服务); - 类型同步靠
yarn gulp downloadPromptTypes(可复用同一个本地路径变量指向本地产物); - 从源码结构看,本地模式在哈希校验、watch 热重载、错误上报三个维度与云端模式行为差异明确,且这些差异在二进制打包阶段会被强制剥离,保证生产路径始终走 Cloud 校验流程;
- 测试分层遵循 monorepo 既有约定,主仓库测试覆盖“加载器”,
cypress-services仓库测试覆盖“bundle 本体”。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00