读取时机:安装、启动、重启、调试或验证
apps/desktop及其共享 packages 时
本文是 Desktop 开发命令及其使用条件的权威说明;可执行脚本以当前 checkout 的根
package.json 与 apps/desktop/package.json 为代码事实源。
Agent 启动 Desktop 只使用仓库根的安全包装命令,并显式选择目标区域:
pnpm restart:desktop:remote --region=global
pnpm restart:desktop:remote --region=cnDesktop 连接的是你自己的 Cindy 云端账号(remote)。这与登录页中免 Cindy 账号的
「跳过登录」(应用内显示为「未登录」,无需账号即可使用本机 agent;代码内部标识仍为
local mode)不是同一个概念。Agent 不得自行改用
pnpm dev:desktop 或 pnpm dev:desktop:remote 绕过包装脚本。
启动包装可能停止已有 Desktop dev 进程。必须尊重宿主提供的并行或保活工作流;如果 脚本因为当前 Agent 运行在 Cindy 内部而拒绝重启,不要换命令绕过,应把提示交给用户。
两个 restart 命令都支持下列参数;用户没提就不要主动加(不带 = 共库 + 正常调度)。 这些参数只对 dev 生效,不影响用户机器上的正式版。
--region=cn|global(默认global):切换构建身份与仓内端点清单;中国大陆版 必须显式传--region=cn,读取config/endpoint.json。--isolated/--isolated=<名字>:使用独立 userData 沙箱,数据库、登录态、会话、定时 任务与设备身份都与正式版彻底隔离(首次需重新登录);命名沙箱每个名字一条独立沙箱, 名字限A-Za-z0-9_-、≤32 字符。用户说「独立数据库/隔离数据/沙箱启动/不要动正式版 数据」时用。未合入主干的 migration 必须在--isolated沙箱里跑,不得连共享 userData (见database-and-migrations.md)。沙箱(及任何 dev userData 覆写)内不触发首登旧数据迁移(mToc):不探测老目录、不弹确认窗、不把正式 数据复制进沙箱。--passive:定时任务被动模式,本实例不自动触发 schedule,但数据仍与其它实例共享; 多开导致定时任务重复、需要让位给 primary 时用。共享 userData 的 passive 实例对 userData 布局保持只读:不执行 owner-namespace 迁移(claim 推迟到下次独占启动), legacy 数据导入(hasLegacyOwnerNamespaceClaim门控的 secret/IM/brain 搬账) 一并等待。非 passive 实例执行该迁移前也会先查.dev-instances实例注册表 (dev 与 packaged 实例都登记——dev 与正式版共库双开受支持),发现其它存活实例 共享同一 userData 时同样推迟——搬家式迁移必须独占 userData 才能执行,否则会打断 还在运行的旧版本实例(2026-07-23 slack-hook.json/网关凭证被搬走事故)。 auth 凭证同属这条契约:passive 共享实例不得删除、作废或消费整机共享的 auth 持久状态——磁盘 refresh token、服务端 device token(调登出会连坐作废 primary 的 那份)、relogin marker(一次性,被消费掉 primary 就再也看不到)、canary flag、账号 删除 receipt。它的「退出登录」只清本进程内存态(authManager.ts的isPassiveSharedUserDataInstance)。代价是同机两个实例的登录态可能不一致,这是有意 的:passive 无权代表整机登出(2026-07-27 事故:MIGRATE_FAILED 的 passive 实例在 fatal 界面点「返回登录」,删掉整机 refresh token,primary 在 19/46 分钟后的续期周期 被强制重登)。 约束的是破坏性动作,不是写入本身:passive 照常排续期 timer,轮换后正常写回新的 refresh token——那写入的是有效凭证,primary 侧由 replacement-retry 消化。反过来让 passive 停止续期,会使它的 access token 过期后再无替换途径(primary 的续期只更新磁盘 token,不更新 passive 进程的内存态,而直接走apiFetch的路径没有 401 refresh/retry)。--preserve-running:并行 dev,不停止任何已有 Cindy dev 进程,每个新实例强制 passive 并共享当前 userData/登录态;仅供能证明实例归属的上层编排,或用户明确「不要关当前 实例/不要重新登录」时用。仅支持 remote,禁止与--isolated组合。
已手动设 XDT_USER_DATA_DIR 时尊重用户值,不覆盖。
restart 命令默认会停掉本 checkout 能识别到的全部 Cindy dev 进程,所以用同一 checkout 的 restart 无法同时多开。真要并行多个 dev 实例,走下面三条之一:
- 由能证明实例归属的上层编排调用
--preserve-running(不停已有实例,共享 userData/ 登录态,可从多个 worktree 重复启动); - 让用户在自己终端直跑 human-only 的
pnpm dev:desktop:remote --isolated=<名字>(不杀旧 进程,每个名字一条完全独立的沙箱); - 使用多个 checkout。
Agent 自身仍只走 restart 命令,不直接调 human-only 的 dev:desktop*。共享同一 userData
多开时,非 primary 实例用 --passive 让出定时任务调度(见上)。
dev 构建默认不初始化 TapDB,与用户是否同意《隐私政策》、统计开关是否打开无关。闸在
main 侧 analytics-settings-store.ts 的 isReportingBuild()(app.isPackaged !== true
默认关),renderer 只消费 allowed 这个结论。
原因:TapDB Web SDK 的设备身份(device_id)写在 renderer 的 localStorage 里,而
localStorage 按 origin + userData 目录 分家——dev 的 renderer 从
http://localhost:<vite 端口> 加载(并行多开时端口自增),--isolated[=<名字>] 与
XDT_USER_DATA_DIR 每条沙箱又各有一份。于是一个开发者一天能凭空造出几十台「新增设备」,
把线上新增设备/转化率/次日留存全部带偏(2026-07-26 复盘:某地区单人一天 78 台设备、
新增账号 1、次日留存 2.6%)。dev 与 release 目前共用同一个 TapDB appId,只能在闸上区分。
要验证上报链路本身时,手动设 XDT_TAPDB_DEV=1 放行(严格等于 1,其它值一律视为关)。
这会把 dev 数据打进线上 app,用完即撤,不要写进任何脚本或 .env。
- 修改 main、preload、MCP、原生依赖或 package 运行时代码后需要重启。
- 只修改 renderer 时优先使用现有实例的热更新,不重复重启。
- 不确定运行实例来自哪个 checkout 时,先运行
pnpm desktop:whoami -- --all核对。
本节指导开发过程中的增量验证;提交(commit/PR)前的强制门禁以
development-workflow.md 的「提交前测试门禁」为准(仓库根 pnpm test:unit 与相关
package 的 typecheck 全部通过)。开发过程中根据实际改动选择最小但充分的检查:
pnpm --filter desktop typecheck
pnpm --filter desktop lint
pnpm --filter desktop exec vitest run <测试文件路径>
pnpm --filter desktop test
pnpm build
pnpm test:unit- 改 TypeScript 至少运行相关类型检查和定向测试。
- 跨模块、共享 package、构建链或广泛重构再扩大到 Desktop 全量测试、构建或根级单测。
- 调整 Desktop Vitest worker 或测试分池前,先读取
desktop-unit-test-performance.md,并用其中的 benchmark 在相同测试范围下做前后对比。 - 数据库 migration、协议、更新器、权限与用户数据另有高风险专项规则;命中时先读取 对应规则,不以本页命令替代专项验证。
- 记录实际执行和结果;未执行的高相关检查必须说明原因。