teleportToRemote 决策树

这页回答什么

teleportToRemote() 到底在决定什么?它如何在 GitHub clone、git bundle、empty sandbox、environment 选择之间做分支,并把本地工作区送到远端 session?

关键文件:

  • src/utils/teleport.tsx
  • src/utils/teleport/gitBundle.ts
  • src/utils/teleport/environments.ts
  • src/tools/AgentTool/AgentTool.tsx

一句话结论

teleportToRemote() 的本质不是“发一个远端请求”这么简单,而是在做一套 source-selection + environment-selection + session-creation 决策树:先决定远端容器如何拿到代码,再决定跑在哪个 environment,最后才创建 session。

这一步几乎决定了 remote agent 能不能正常工作。


1. 它要解决的真实问题

remote agent 启动时,远端 session 至少要知道三件事:

  1. 代码从哪里来
  2. 跑在哪个 environment
  3. 初始消息 / outcome branch / session metadata 是什么

所以 teleportToRemote() 本质上是在回答:

如何把当前本地上下文投送成一个远端可执行 session。


2. 整体决策可以分成两大分支

A. 显式 environmentId 分支

这是更“受控”的模式。

特点:

  • 跳过普通 environment discovery
  • 仍然会做 repo/source 决策
  • 可注入 environmentVariables
  • 可显式要求 useBundle

B. 普通自动选择分支

这是默认 remote agent 路径。

特点:

  • 自动决定 source ladder
  • 自动拉 environments
  • 自动选择 environment
  • 再构造 create-session 请求

3. 显式 environmentId 分支:更像“定向投送”

options.environmentId 存在时:

先做什么

  • 校验 OAuth / org UUID
  • 准备 headers
  • 构造 envVars
    • 自动带 CLAUDE_CODE_OAUTH_TOKEN
    • 再 merge options.environmentVariables

再决定 source

如果 useBundle === true

  • 直接 createAndUploadGitBundle(...)
  • 拿到 seedBundleFileId
  • 远端从 bundle 克隆

否则

  • detectCurrentRepositoryWithHost()
  • 如果能识别仓库,就构造 gitSource
  • 远端按 git repository source checkout

最后 create session

requestBody 的关键部分是:

  • session_context.sources
  • session_context.seed_bundle_file_id
  • session_context.environment_variables
  • environment_id

这说明显式 env 分支的特点是:

environment 是调用方指定的,source 是在这个前提下再补进去。


4. 自动分支的第一步:source selection ladder

自动分支里最核心的注释已经把 ladder 说清楚了:

GitHub clone (if CCR can actually pull it)
  → bundle fallback (if .git exists)
  → empty sandbox

这是整个 remote 设计最重要的路径。


5. GitHub clone 路径不是盲选,而是先 preflight

自动分支会先:

  • detectCurrentRepositoryWithHost()
  • 判断 repo host
  • 如果是 github.com,先跑 checkGithubAppInstalled(...)

它不是“看见 GitHub remote 就直接用”。

这是个很老练的判断,因为如果远端根本没有能 clone 这个 repo 的 token,直接起 session 只会得到一个必死容器。

所以它先把状态分成:

  • github_preflight_ok
  • github_preflight_failed
  • ghes_optimistic
  • no_github_remote
  • forced_bundle
  • no_git_at_all

这批 sourceReason 本质上就是 source 决策的解释码。


6. 什么时候走 Git source

如果满足:

  • repoInfo 存在
  • ghViable 为真

就构造:

  • gitSource = { type: 'git_repository', url, revision, ... }
  • 可选 allow_unrestricted_git_push
  • 同时构造 gitOutcome

这里还会决定:

  • revision = explicit branchName || default branch
  • reuseOutcomeBranch 时允许直接往调用方分支推

也就是说 Git source 路径不仅是在“带代码过去”,也是在给 remote session 定义一个 git outcome 策略。


7. 什么时候走 bundle fallback

如果:

  • GitHub 路径没走通
  • bundle gate 开着
  • 本地存在 .git

就进入:

  • createAndUploadGitBundle(...)
  • 得到 seedBundleFileId

这里 bundle 的语义不是普通归档上传,而是:

把当前本地仓库状态(包括某些未提交工作树场景)封装成 seed bundle,交给远端作为 clone 起点。

这条路径特别重要,因为它让 remote agent 不再严格依赖:

  • GitHub remote
  • GitHub App 完整安装
  • 远端可直接 clone 公私仓

所以 bundle 是 remote teleport 的真实兜底机制。


8. 什么时候会退化成 empty sandbox

如果最后:

  • 没有 gitSource
  • 也没有 seedBundleFileId

代码会明确记录:

  • No repository detected — session will have an empty sandbox

这说明 remote session 仍然可以启动,只是:

  • 没有 repo checkout
  • 更像一个空工作区容器

所以 teleport 的 source 选择不是二元成败,而是三态:

  • github
  • bundle
  • empty

这一点很像成熟系统的工程做法,不因为没有 repo 就整个 remote 功能报废。


9. 自动分支的第二步:environment selection

source 决策完成后,自动分支还要:

  • fetchEnvironments()
  • 决定选哪个 environment

优先顺序大致是:

  1. 配置中的 defaultEnvironmentId(如果允许)
  2. anthropic_cloud
  3. 非 bridge 的第一个环境
  4. 实在不行就第一个环境

还有一个很细的保护:

  • 如果调用方要求 useDefaultEnvironment 但列表里没有 anthropic_cloud
  • 会 retry 一次 fetchEnvironments()
  • 还不行就 fail fast

这说明 environment 选择也不是随便捡一个能用就上,而是尽量避免 silently 掉进死环境。


10. source 与 environment 是解耦但连续的两层决策

这个函数最值得注意的设计点之一是:

先决 source

  • 远端怎么拿代码

再决 environment

  • 代码在哪个远端环境执行

它们不是一回事,但被串成一个 session creation pipeline。

这让系统可以表达很多组合:

  • GitHub source + anthropic cloud env
  • bundle source + explicit code review env
  • empty sandbox + default env

所以 teleport 不是单点判断,而是一个小型 deployment planner。


11. telemetry 暗示这其实是产品级关键路径

代码里有:

  • tengu_teleport_bundle_mode
  • tengu_teleport_source_decision

说明 source path:

  • 走 GitHub
  • 走 bundle
  • 退空 sandbox

不是无足轻重的内部细节,而是产品上被持续观测的重要决策。

换句话说,团队自己也知道:

remote agent 好不好用,很大程度就取决于 teleport 这一步选得对不对。


12. 最简决策图

teleportToRemote()
  → OAuth / org UUID
  → if explicit environmentId:
       → build env vars
       → choose bundle or git source
       → create session directly
    else:
       → detect repository
       → source ladder:
            github clone if viable
            else bundle if possible
            else empty sandbox
       → fetch environments
       → choose environment
       → create session

如果再压缩成 source 决策图,就是:

repo + github viable ? github source
: repo/.git + bundle gate ? seed bundle
: empty sandbox

当前结论

teleportToRemote() 的工程意义在于:

它把 remote agent 启动前最容易失败的现实问题,收束成一条明确的决策树。

这条树至少处理了:

  • GitHub auth 不可用
  • 只有本地 git 仓库、没有可 clone remote
  • 无 repo 场景
  • 指定 environment 的特种流程
  • environment list 不稳定

所以 remote agent 能跑起来,真正的前线不是 RemoteAgentTask,而是 teleport 阶段。


下一步最值得继续追

  1. 单独深挖 createAndUploadGitBundle() 如何处理未提交工作区
  2. 追 create-session request body 里 outcomes / gitOutcome 的语义
  3. AgentTool local async / remote / sync 三条路整理成统一总图