开发
首次检出
安装 vp,方法见 根目录 README。它会提供 package.json 中锁定的 Node 版本(Node 26),因此不需要再单独安装 Node。在仓库根目录执行:
vp i
vp run dev打开开发运行器打印出来的配对 URL。不带令牌的源站地址无法让新的浏览器完成认证。
更倾向于用容器?VS Code 与 Codespaces 的配置见 开发容器。
GitCafe 镜像(可选)
GitHub 是权威来源。git.cafe/pingdotgg/t3code 是双向同步的镜像,API 速率限制高得多,多个智能体同时工作时会有帮助。贡献代码并不需要用到它。
在 GitCafe 上做的推送、拉取请求(PR)、评论、评审、标签和议题编辑,大约一分钟内会同步到 GitHub,并且 PR 与议题的编号保持一致。CI 在 GitHub 上运行,检查结果会显示在 GitCafe 上。
git remote set-url origin https://git.cafe/pingdotgg/t3code.git
git remote add github https://github.com/pingdotgg/t3code.git
gh repo set-default pingdotgg/t3code # `gh` is still used for merging
bun install -g @gitcafe/cli@latest # `cafe` needs Bun 1.2 or newer
cafe auth login && cafe auth http setup
cafe skills install --global # optional: cafe-* skills for coding agents当 origin 位于 git.cafe 上时,通过 GitCafe 完成操作,不去调用 GitHub 的 API:
- 推送到
origin,用cafe pr create开 PR(从 fork 则用--head owner/repo:branch),或用cafe stack submit,之后的操作一律使用cafe。凡是有工具跟踪该 PR 的地方,都填写 git.cafe 的 PR URL,而不要填写 github.com 的 URL,这样轮询会留在 GitCafe。 - 用
cafe pr status <n>读取检查结果,用cafe ci logs <run>查看 GitHub Actions 日志(运行 ID 来自cafe ci list --pr <n>)。cafe ci rerun <run>会重新运行其中一次。这些命令需要cafe0.11 或更新版本。 - 用于关闭议题的引用写成
Fixes pingdotgg/t3code#<n>。GitCafe 会把同步到 GitHub 的文本里单独出现的#<n>变成 git.cafe 链接,GitHub 不会根据这类链接关闭议题。 - 使用
gh pr merge <n> --match-head-commit <sha>合并,因为cafe pr merge无法固定你已经评审过的那次提交。 cafe的命令与gh不同:是cafe issue comments <n>而不是--comments,是cafe pr status而不是pr checks。
选择开发进程
服务端和 Web 使用 vp run dev,Electron 客户端使用 vp run dev:desktop。dev:server 和 dev:web 会把这些进程分开启动。原生构建和 Metro 见 移动端 README。
标志紧跟在任务名后面,例如 vp run dev --home-dir /tmp/t3code-dev。加上 --browser 可以自动打开浏览器。
要在某个工作树(worktree)的对话线程(thread)收束时停掉它的开发栈,请在「设置 > 项目 > 操作」里从 t3.json 导入 Dev 操作,再通过操作菜单或它的快捷键(keybinding)启动。它的 stopOnSettle 选项会让 T3 停止该终端及其子进程。通过智能体自己的 shell 工具运行 vp run dev,不会把它纳入这个生命周期。
状态与端口
已链接的工作树默认使用各自的 .t3/userdata,即便设置了 T3CODE_HOME 也是如此。主检出默认使用 ~/.t3/dev。两种情况下,显式的 --home-dir 都优先。工作树首次启动时,如果来源不超过 500 MiB,会用 ~/.t3/userdata/statev2.sqlite 的只读副本给空数据库灌入种子数据。来源更大时会给出警告,开发进程以空数据库启动。--no-seed 或 T3CODE_DEV_SEED=0 会跳过灌入种子数据。要复制更大的来源,请在停止开发进程后运行 vp run migrate-dev-db。修剪之前,它可能占用大量磁盘空间。切勿让开发服务器对着正在使用的 ~/.t3/userdata 运行。复制一份一致的数据库快照见 测试数据。
从 [dev-runner] 的输出中读取端口。工作树会根据自身路径得出稳定的端口偏好,但已被占用的端口可能使实际端口发生偏移。需要时可用 T3CODE_PORT_OFFSET 或 T3CODE_DEV_INSTANCE 选择另一组偏好。
回放提供方(provider)
vp run dev 会注册 Replay (dev),这是位于 dev-replay-agent.ts 的本地 ACP 智能体。它用模拟的流式传输播放已录制的 ACP 记录,因此真实的适配器(adapter)、编排和界面可以在没有付费模型的情况下运行。每个场景对应一个模型,每条提示会播放下一段已录制的回合(turn)。捆绑式服务器(bundled server,包括桌面端开发)会跳过它,因为它们没有智能体脚本。开发服务器会在启动时把它加回来,因此请在设置中关闭它,而不是删掉这项注册。要添加场景,把一条 SCENARIOS 指向通用的 ACP 回放夹具(registry_transcript.ndjson),或 dev-replay 中的某个文件。该智能体会回放提供方会话(session)更新、权限请求和提问。它会丢弃会话建立过程和厂商帧,因此 Grok 与 fx 的录制会失去仅厂商侧才有的行为。
分析开发启动
运行 vp run profile:startup,在 .t3/startup-profiles/<timestamp> 下捕获继承而来的 Node 进程的前 20 秒。执行预加载的工作进程会得到各自独立的性能记录。它会启动常规的开发运行器,并接受该运行器的标志。捕获结束后用 Ctrl+C 停止。设置 T3CODE_STARTUP_PROFILE_DIR 可以选择其他输出目录。性能分析会增加开销,因此请用一次普通的 vp run dev 运行来对比就绪时间。
在 Chrome DevTools 的 Performance 面板中打开 .cpuprofile,可以查看采样得到的函数栈和源码行。每个 .modules.json 都是一份 Chrome 追踪记录,包含带计时的模块解析、加载、同步子进程调用以及启动测量。按文件的记录包括导入方 URL、源码加载、准备、递归依赖链接和顶层求值。准备阶段使用同步加载器边界;链接阶段包含依赖,CommonJS 求值则可能包含嵌套的 require 调用。部分 ESM/CommonJS 互操作会把阶段合并后报告。.process.json 记录进程、Node 版本和捕获窗口。服务器中嵌套的 Effect 与 SQL span 仍保留在 .t3/userdata/logs/server.trace.ndjson 里。
预加载会临时加入模块体的计时标记,并借助一次自动的调试器暂停,为正在运行的 Node 加载器插桩。启动器会把 --no-node-snapshot 加入继承的 NODE_OPTIONS,因为 Node 26 中已打入快照的加载器无法接受该断点。直接使用预加载时也要带上同一标志。这会禁用 Node 内置的启动快照,可能改变编译缓存的命中情况,并增加开销。按文件的捕获应视为诊断运行;测量基线启动延迟时使用未插桩的运行。第一个入口模块可能在加载器插桩安装完成之前就已经编译。
全新进程仍会使用文件系统缓存、Node 编译缓存、提供方缓存和 Vite 依赖缓存。缺少 Vite 依赖缓存时的测量,要与已有数据库情况下的重启分开进行。Vite 就绪消息和 HTTP 监听器都不足以证明浏览器已经渲染出应用。
共享与远程调试
vp run dev --share 会把 Web 端口发布到本机的 tailnet 上,并打印该源站的配对 URL。把完整 URL 交给测试者,包括其中的令牌。开发运行器退出时会移除该映射。
让 VITE_HTTP_URL 和 VITE_WS_URL 保持未设置。Vite 会经由浏览器的源站代理后端,因此同一份构建在 localhost 和远程连接下都能工作。
共享运行会启用捆绑式开发,以免每一层导入都产生一次网络往返。调试打包器差异时,用 T3CODE_BUNDLED_DEV=0 退出该模式。改动这套配置时,有两处重载陷阱需要注意:
- Web 入口必须动态导入应用,这样 React Refresh 会在应用代码块之前完成初始化。静态导入可能在首次加载时正常,在路由拆分之后失败。
- 捆绑式开发通过受监视的文件重建 Tailwind。它常规的 Vite 热更新钩子期望一份服务器模块图,而 Rolldown 并不提供这份图。
- 应用及其路由是懒加载代码块,在首次请求时构建。服务器启动时,有一个插件会预打包启动链。重命名或移动某个启动路由或应用入口,会在没有提示的情况下把它从该列表中去掉。
这些变通做法位于 Web 入口、Tailwind 插件 和 启动预热。
vp run dev --share --built 会跳过 Vite 开发服务器。它为 Web 应用运行 vp build --watch,后端则在同一条 tailnet URL 和同一套配对流程上提供 apps/web/dist。有人通过慢速链路测试,或只需要使用应用时,就用这种方式:加载体积与生产构建相当,首次加载也不必等待开发期打包。没有 HMR。编辑之后,等到出现 built in 这一行(大约 6 秒),再刷新。在第一次构建出现之前,该 URL 会显示一个会自行刷新的页面。此模式下不会生成许可证清单。--built 需要 --share,以及 dev 模式。
可复用的开发凭据
只在你信任该主机名上每一项服务时才使用。浏览器会把 Cookie 发送到该主机名的所有端口。你在该主机名上访问的任何服务都可能收到这份可复用的管理员凭据,包括与 T3 Code 无关的服务。如果该主机名上运行着不受信任的服务,请继续使用按环境(environment)分别配对的常规方式。
要让同一主机名上的各个 Web 开发工作树共用一个浏览器配置文件,请一次性生成一个固定值:
openssl rand -hex 32把该值写入主检出中已被 git 忽略的 .env:
T3CODE_DEV_AUTH_TOKEN=<the value generated above>t3.json 中的 Setup Worktree 操作会把该文件链接到每个工作树的 .env。开发运行器在启动时读取仓库的环境文件。.env.local 以及继承而来的进程环境变量会覆盖 .env,因此设置完成后不必再按工作树导出。
如果是没有该链接的手动工作树或启动器,改为导出同一个固定值:
export T3CODE_DEV_AUTH_TOKEN="<the value generated above>"不要在启动时生成新值。配置完成后启动或重启 vp run dev --share,然后在该主机名上,每个浏览器配置文件打开一次它打印出来的启动配对 URL。之后,同一主机名上的 Web 开发服务器会跨端口接受这份共享 Cookie。该 Cookie 在 30 天后过期。如果旧标签页的 URL 现在提供的是被替换掉的环境,请重新加载该标签页。
令牌和启动配对 URL 都是可复用的管理密钥。切勿把它们放进提交、拉取请求(pull request)或公开输出中。每台服务器在启动时仍会写入自己的认证数据库记录,并保有自己的 SQLite 数据、签名密钥和吊销状态。桌面服务器和非开发服务器会忽略该值。安全模型见 环境认证。
检查
vp run check:changed 会针对你分支上的改动,运行 CI 的快速检查:已改文件的格式化与 lint;代码或清单发生变化时的 knip:check;生成的 GraphQL 检查;受影响包及其下游依赖包的类型检查;以及改动文件旁边的测试文件。它还会运行那些需要读取其他文件的检查:服务器受影响时的智能体上下文预算,以及只针对已改动的 Web 与移动端文件的 React Compiler 覆盖率。它用 ci-changes.cjs 把文件映射到包,做法与 CI 相同。比较基准是任一远程的 main 上最近的合并基,因此即使某个 fork 的 main 落后于上游,比较对象仍然是上游;也可以改用你传入的引用。CI 仍会运行完整测试套件以及摘要里列出的任务,因此本地通过并不保证 CI 运行会通过。
这些步骤并行运行,同时进行的数量以机器能力为限:每两个核心一个,每 6 GiB 可用内存一个。在大型机器上,一次运行的耗时大约等于最慢的那个步骤,通常是 knip 的导出检查,大约 10 秒。传入 --concurrency <n> 或设置 CHECK_CHANGED_CONCURRENCY 可以改变这个上限(1 会把步骤逐个运行)。某个步骤的运行时间超过 --timeout <seconds> 或 CHECK_CHANGED_TIMEOUT(默认 900)时,会被连同它启动的全部进程一起终止,并报告为 TIMEOUT,而不是一次失败的检查。当文件与上一次通过的运行完全相同时,它会打印那次运行的摘要,而不再重新运行。文件、安装或生成文件只要有变化,就会重新运行检查。
迭代时可以单独运行各项检查:
vp test run <files>
vp lint <files>
vp run --filter <package> typecheck在 apps/web 中,渲染 React 或接触 DOM 的测试命名为 *.browser.test.{ts,tsx},并在无头 Chromium 里、开启 React Compiler 的情况下运行,与应用本身一致。这里没有 jsdom;其他 *.test.ts 文件在 Node 中运行。
TypeScript 工作区使用 effect-tsgo tsrs --noEmit(来自 @effect/tsgo)做类型检查,也就是内置了 Effect 诊断、快速修复和重构的 tsrs 检查器。只有 apps/marketing 会安装 typescript,供 Astro 使用。VS Code 的 TypeScript 7 扩展通过 .vscode/tsdk/tsgo(js/ts.tsdk.path,一个 POSIX 脚本)启动同一个检查器,因此在 Windows 上请经由 WSL 打开工作区。
原生移动端的改动使用 vp run lint:mobile。完整套件由 CI 负责;当前任务见 ci.yml。在该套件还不是必需门禁时,可以用 手动 Windows 通道 做针对性的 Windows 排查。
未使用的代码
vp run knip:check 会检查整个仓库中未使用的文件和依赖,同时检查 apps/server、apps/desktop、apps/web 以及 packages/ 下每一个内部包中未使用的运行时导出。CI 会强制执行这两项检查。导出的类型和 Effect schema 允许没有使用方。schema 预处理器能够识别 schema 类型,包括别名和 schema 类;创建或解码 schema 的函数仍会受到检查。规范的 Effect 服务构造 API 借助显式的 @public 注解保持导出,Knip 能够识别该注解。完全未使用的文件同样仍在检查范围内。Web UI 组件模块中的具名导出按完整的组件集合保留。Knip 会忽略 apps/web/src/components/ui/*.tsx 中未使用的导出,但仍会报告整个文件未被使用的情况。用 vp run knip --workspace apps/web 审计单个工作区(包括导出),或用 vp run knip:production --workspace apps/web 找出仅因测试而保留下来的代码。完整的导出审计仍有发现项,并且不是仓库范围的 CI 门禁。随着更多工作区变得干净,再扩展导出检查的工作区选择器。删除代码前先审查调用方;生产模式也可能报告开发脚本和测试夹具。运行时发现的入口点以及依赖例外写在 knip.jsonc。
桌面产物
本地产物构建默认不签名,输出写入 release/:
vp run dist:desktop:dmg
vp run dist:desktop:linux
vp run dist:desktop:windist:desktop:linux 的目标是 x64。原生 Linux ARM64 构建请运行:
vp run dist:desktop:artifact --platform linux --target AppImage --arch arm64DMG 默认采用宿主架构。用 --arch 选择其他目标,用 --keep-stage 保留打包文件以便检查。其他选项请运行 vp run dist:desktop:artifact --help。
预先准备的服务器字节码
原生 macOS 与 Windows 桌面产物构建,以及原生 Linux x64 与 ARM64 构建,会在创建 ASAR 之后、签名之前,用打包后的 Electron 可执行文件生成服务器字节码。生成过程会导入 CLI 和服务器模块声明,而不启动服务器服务;仅供工作进程使用的模块按常规方式编译。AppImage 后端为每次挂载使用临时链接,并在正常退出时移除它们。之后的启动会回收那些链接指向已消失挂载的遗弃缓存目录;AppImage 主进程缓存保持关闭。在 ARM64 宿主上构建的 macOS x64 会在 Rosetta 下运行打包后的 x64 Electron 来生成缓存。其他跨平台和跨架构构建会显式跳过生成。CLI、SEA 和 WSL 运行时保持既有行为。
Electron 的 Node 24 缓存没有只读选项,目录名中包含生产者 UID。后端以已安装模块路径为键,把各个预先准备好的文件链接进自己的可写缓存。Node 以原子方式替换文件,因此重新生成不能透过这些链接写进已签名资源。Windows 使用硬链接;当卷不同,或资源权限阻止建立链接时,回退到常规缓存。因此受保护的安装可能无法复用预先准备的字节码。启动期间不会复制任何缓存字节。
键的计算遵循 Node 内部的可移植 ESM 格式。生成过程会检查打包后的引擎是否产出了预期的 CLI 和服务器条目,若该格式发生变化则构建失败。清单还会检查 Electron、Node、操作系统、架构,以及 V8 对标志敏感的版本标记。缺失或被拒绝的条目会从随发行版提供的 JavaScript 编译。已预先准备的后端会跳过启动时的显式刷写;Node 可以在正常退出时把缺失条目持久化。可写回退路径仍保留显式刷写,以应对 Windows 强制结束后端的情况。
Linux AppImage 前置条件
请在 Linux 上构建,因为浏览器密钥辅助程序会链接宿主上的 libsecret。安装 C/C++ 构建工具、libsecret 开发头文件、pkg-config 和 ImageMagick。字节码准备还会运行打包后的 Electron,因此需要 GTK、NSS、ALSA 和 GBM 运行时库。通过 rustup 安装 Rust 1.95 或更新版本,并像发布 CI 那样使用 stable 工具链。
Ubuntu 24.04 与 Debian 13:
sudo apt-get update
sudo apt-get install build-essential libsecret-1-dev pkg-config imagemagick libgtk-3-0t64 libnss3 libasound2t64 libgbm1在 Ubuntu 22.04 和 Debian 12 上,用 libgtk-3-0 和 libasound2 代替 t64 软件包。
Fedora:
sudo dnf install gcc gcc-c++ make libsecret-devel pkgconf-pkg-config ImageMagick gtk3 nss alsa-lib mesa-libgbmArch Linux:
sudo pacman -S base-devel libsecret pkgconf imagemagick gtk3 nss alsa-lib mesaLinux 桌面开发同样需要 C 工具链、pkg-config 和 libsecret 头文件。
macOS DMG 前置条件
用 xcode-select --install 安装 Xcode 命令行工具,并安装 Rust。跨架构或通用构建时,添加所需的 Rust 目标:
rustup target add aarch64-apple-darwin x86_64-apple-darwinWindows 安装程序前置条件
安装 Rust、Python 3,以及带有 使用 C++ 的桌面开发(Desktop development with C++)的 Visual Studio Build Tools。为目标架构包含 Windows SDK、MSVC 构建工具,以及经 Spectre 缓解的库。添加对应的 Rust 目标:
rustup target add x86_64-pc-windows-msvc
# For an ARM64 installer:
rustup target add aarch64-pc-windows-msvcNSIS 由 electron-builder 下载。WSL 支持还需要把 Linux CLI 归档作为 --wsl-runtime 传入;参见 发布操作手册。
签名与通行密钥
配好平台凭据之后加上 --signed,配置步骤见 发布操作手册。macOS 通行密钥需要已签名且已预配的应用;本地签名和渲染进程 HMR 请遵循 Connect 配置。