Headless 守护只用本目录(CLI 二进制 + 整包 pkg/),不扫 Copilot.app Caches,也不读 PATH 上的 copilot。(CLI 会在 pkg/ 里写 inuse.<pid>.lock 占用标记、vendor 时会清掉,属正常。)
runtime/
VERSION # 当前钉死的版本号(进 Git)
<ver>/cli/copilot # 宿主二进制(gitignore)
<ver>/pkg/ # 同版本整包(gitignore)——它**就是** CLI 的 JS 运行时
copilot-sdk/
preloads/extension_bootstrap.mjs
… # app.js / builtin / assets / wasm 等,完整整包
headless-daemon.sh 实际执行:
COPILOT_CLI_DIST_DIR=runtime/<ver>/pkg # ← CLI 的 JS 运行时也钉在这里
COPILOT_AUTO_UPDATE=false # 禁掉自动下载/自动换版本
runtime/<ver>/cli/copilot runtime/<ver>/pkg/preloads/extension_bootstrap.mjs
# COPILOT_SDK_PATH=runtime/<ver>/pkg/copilot-sdk为什么需要 COPILOT_CLI_DIST_DIR:宿主 CLI 是 SEA,默认会把自己内嵌的 copilot.tgz 自解包到 ~/Library/Caches/copilot/pkg/<plat>/<ver>/,并从那里 import index.js;而 index.js 的 resolveBootstrapPath() 又优先用该目录的 preloads/extension_bootstrap.mjs,argv 传进去的路径会被丢弃。于是「只软化 vendored 副本」= 打给永不执行的文件,原版父进程门闩会静默 exit(0) → launchd 崩循环(2026-09-13 升级 1.0.83 就是这么炸的)。
COPILOT_CLI_DIST_DIR 把 __dir 钉回 runtime/<ver>/pkg,argv 那份就是生效文件,全程不读 Caches。
--prefer-version(那个 flag 会让 DIST_DIR 直接被忽略)。
二进制不进 Git;只跟踪 VERSION 与本说明。
# ① 只看不升:上游 changelog + 6 个关键 API 面 diff(不碰运行中的守护)
# ≥1.0.90 平台包只剩二进制 → unpkg 404 时自动下包自解包取 SDK(~170MB)
bash scripts/preflight-sdk-diff.sh [版本]
# ② 确认后才升级(脚本自己会先跑 ①;关键面有变化要**完整输入 yes**;非 TTY 环境须显式 --yes)
bash scripts/vendor-copilot-runtime.sh [版本] [--yes] [--no-preflight]
# 脚本还会:拉 npm 平台包 → sha1 校验 → 停守护 → 换 runtime → 软化 bootstrap → 写 VERSION → 自动拉起 → 打 status- 镜像用
NPM_REGISTRY(默认https://registry.npmjs.org);平台包约 170MB(几乎只有二进制),自解包后runtime/<ver>/约 350MB+。 - 规程(六个核对面 / 退出码 0-1-2-3 / 场景表):
../doc/sdk-upgrade.md。
换完先跑隔离烟测(临时 COPILOT_HOME + 独立 spawn,完全不碰生产):
node scripts/probe-isolated-session.mjs --send # 生产式(保留身份)
node scripts/probe-isolated-session.mjs --no-auth --send # 真·无身份
# 五步:start / getAuthStatus / listModels / createSession / send;退出码 0 = 通过验证换版是否生效:status 里 dist=runtime/<ver>/pkg 且 align=vendored:<新版本>;
daemon.log 里应出现 resolveBootstrapPath: __dir=…/runtime/<ver>/pkg(而不是 Caches)。
pkg/ 的正文取自 CLI 二进制内嵌的 copilot.tgz:脚本临时跑 cli/copilot --version(COPILOT_PKG_CACHE_HOME 指临时目录),再 rsync 过来。≥1.0.90 平台包本身几乎只有二进制,没有可直接拷的 copilot-sdk/;必须走自解包。package.json 名也要以自解包为准(app.js 会读它)。
只清垃圾:.DS_Store / ._* / inuse.*.lock / 旧 preloads/*.bak-compat-*;builtin/、builtin-skills/、assets/、changelog.json、.extraction-complete 全部保留(约 1.4MB,剥掉会静默失败)。
npm i -g @github/copilot / brew 装到 PATH 的仍不够:守护不读 PATH,要的是 runtime/<ver>/cli + 整包 pkg/。vendor 脚本拉的是 @github/copilot-<plat>(现在包里几乎只有 copilot 二进制;pkg/copilot-sdk + preloads/ 由该二进制自解包得到),不是薄包装 @github/copilot(只有 npm-loader.js)。
离线备选(日常升级不需要,~/Library/Caches/copilot/pkg 已清空):
bash scripts/vendor-copilot-runtime.sh --from-cache:仅当本机还有 App 解过的 Caches 时才可用(pkg/正文同样取自自解包,不用那份缓存)- 或从另一台已 vendor 的机器拷
runtime/<ver>/,把VERSION写成该<ver>
runtime/ 只解决「有没有成对 CLI+pkg」;身份是另一件事,而现在默认故意不要身份:
| 配置 | 行为 |
|---|---|
config/models.json → auth.login: false(当前) |
不登录:启动时清掉身份 env(COPILOT_GITHUB_TOKEN/GH_TOKEN/GITHUB_TOKEN/COPILOT_API_KEY)+ SDK 给 runtime 传 --no-auto-login(CLI 原话:Disable automatic login detection (stored OAuth tokens and gh CLI))。官方模型面自动视为关闭。 |
auth.login: true |
保留 GitHub 身份、可用官方模型(另需 display.officialModels.enabled: true) |
实测(2026-09-13 隔离 + 生产):无身份时 start / createSession / BYOK 回合全通,只有官方模型面不可用(listModels → Not authenticated)—— 而它已被自动跳过。自证日志(每次连接都打):
telegram-bridge: GitHub 登录已关闭(auth.login=false)→ 清除身份 env: COPILOT_API_KEY;runtime 将以无身份启动(SDK 传 --no-auto-login)
telegram-bridge: [Headless] auth: isAuthenticated=false (Not authenticated)
要切回官方模型:auth.login: true + display.officialModels.enabled: true → restart,并用本目录的 CLI 重登(不要用 PATH 上另一份):
cd ~/.copilot/extensions/copilot-telegram-bridge
VER=$(tr -d '[:space:]' < runtime/VERSION)
# ⚠️ 必须带 COPILOT_CLI_DIST_DIR,否则 CLI 会自解包重建 ~/Library/Caches/copilot/pkg(该缓存已删)
COPILOT_CLI_DIST_DIR="$PWD/runtime/$VER/pkg" COPILOT_AUTO_UPDATE=false "runtime/$VER/cli/copilot" login
bash scripts/headless-daemon.sh restart排查 401 的先验知识:身份来源顺序 COPILOT_GITHUB_TOKEN → GH_TOKEN → GITHUB_TOKEN → gh CLI → keychain;
CLI 只认 fine-grained PAT(需 Copilot Requests 权限)/ OAuth,不认经典 ghp_。
CopilotClient({ mode: "empty" })(无头在用)会让 SDK 给 runtime 注入 COPILOT_DISABLE_KEYTAR=1(关掉 keychain)
⇒ 遇到 401 优先查 env token。细节见 ../doc/headless-daemon.md §6 与 ../doc/models-config.md §1.1。