@jsonstudio/rcc 0.89.1548 → 0.89.1562

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/configsamples/provider/tabglm/config.v1.json +9 -14
  2. package/dist/build-info.js +3 -3
  3. package/dist/build-info.js.map +1 -1
  4. package/dist/cli/commands/camoufox.d.ts +34 -0
  5. package/dist/cli/commands/camoufox.js +107 -0
  6. package/dist/cli/commands/camoufox.js.map +1 -0
  7. package/dist/cli/commands/code.js +46 -6
  8. package/dist/cli/commands/code.js.map +1 -1
  9. package/dist/cli/commands/restart.js +3 -3
  10. package/dist/cli/commands/restart.js.map +1 -1
  11. package/dist/cli/commands/start.js +6 -5
  12. package/dist/cli/commands/start.js.map +1 -1
  13. package/dist/cli/register/camoufox-command.d.ts +20 -0
  14. package/dist/cli/register/camoufox-command.js +22 -0
  15. package/dist/cli/register/camoufox-command.js.map +1 -0
  16. package/dist/cli.js +11 -0
  17. package/dist/cli.js.map +1 -1
  18. package/dist/commands/validate.js +1 -1
  19. package/dist/commands/validate.js.map +1 -1
  20. package/dist/docs/daemon-admin-ui.html +144 -1
  21. package/dist/manager/modules/quota/provider-quota-daemon.events.js +115 -0
  22. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  23. package/dist/manager/modules/quota/provider-quota-daemon.js +1 -0
  24. package/dist/manager/modules/quota/provider-quota-daemon.js.map +1 -1
  25. package/dist/manager/quota/provider-quota-center.d.ts +7 -1
  26. package/dist/manager/quota/provider-quota-center.js +5 -1
  27. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  28. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +1 -0
  29. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
  30. package/dist/server/runtime/http-server/request-executor.js +42 -7
  31. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  32. package/dist/token-daemon/server-utils.js +1 -1
  33. package/dist/token-daemon/server-utils.js.map +1 -1
  34. package/dist/tools/provider-update/fetch-models.js +0 -1
  35. package/dist/tools/provider-update/fetch-models.js.map +1 -1
  36. package/dist/tools/provider-update/key-probe.js +0 -1
  37. package/dist/tools/provider-update/key-probe.js.map +1 -1
  38. package/docs/PROVIDERS_BUILTIN.md +3 -2
  39. package/docs/daemon-admin-ui.html +144 -1
  40. package/docs/providers/antigravity-fingerprint-ua-warmup.md +116 -2
  41. package/docs/providers/antigravity-gemini-provider-compat.md +317 -0
  42. package/docs/providers/gemini-provider.md +1 -0
  43. package/docs/providers/tabglm-claude-code-compat.md +31 -0
  44. package/docs/stop-message-auto.md +1 -0
  45. package/package.json +4 -5
  46. package/scripts/ci/repo-sanity.mjs +50 -0
  47. package/scripts/responses-compare-server.mjs +1 -1
@@ -885,6 +885,7 @@
885
885
  <label for="oauthAuthAliasInput">alias</label>
886
886
  <input id="oauthAuthAliasInput" type="text" placeholder="default" style="width: 240px;" />
887
887
  </div>
888
+ <div id="oauthAuthIssueHint" class="notice" style="display:none; margin-bottom: 10px; white-space: pre-wrap;"></div>
888
889
  <div class="row" style="margin-bottom: 10px;">
889
890
  <label><input id="oauthOpenBrowser" type="checkbox" checked /> open browser</label>
890
891
  <label><input id="oauthForceReauth" type="checkbox" /> force reauthorize</label>
@@ -1957,18 +1958,129 @@
1957
1958
  $("authCookieBox").style.display = mode === "cookie" ? "block" : "none";
1958
1959
  }
1959
1960
 
1961
+ function findAuthIssueForProviderAlias(providerRaw, aliasRaw) {
1962
+ const provider = textOf(providerRaw || "").trim().toLowerCase();
1963
+ const alias = textOf(aliasRaw || "").trim().toLowerCase();
1964
+ if (!provider || !alias) return null;
1965
+ const list = Array.isArray(UI.quotaProviders) ? UI.quotaProviders : [];
1966
+ if (provider !== "antigravity") return null;
1967
+ const prefix = `antigravity.${alias}.`;
1968
+ for (const q of list) {
1969
+ const pk = textOf(q && q.providerKey ? q.providerKey : "").trim().toLowerCase();
1970
+ if (!pk.startsWith(prefix)) continue;
1971
+ const issue = q && q.authIssue && typeof q.authIssue === "object" ? q.authIssue : null;
1972
+ if (issue && issue.kind === "google_account_verification") {
1973
+ return { kind: issue.kind, url: textOf(issue.url || ""), message: textOf(issue.message || "") };
1974
+ }
1975
+ }
1976
+ return null;
1977
+ }
1978
+
1979
+ function updateOauthAuthIssueHint() {
1980
+ const el = $("oauthAuthIssueHint");
1981
+ if (!el) return;
1982
+ const provider = textOf($("oauthProviderSelect").value || "").trim().toLowerCase();
1983
+ const alias = textOf($("oauthAuthAliasInput").value || "default").trim().toLowerCase();
1984
+ const issue = findAuthIssueForProviderAlias(provider, alias);
1985
+ if (!issue) {
1986
+ el.style.display = "none";
1987
+ el.textContent = "";
1988
+ return;
1989
+ }
1990
+ if (issue.kind === "google_account_verification") {
1991
+ const url = issue.url ? `\n\nOpen: ${issue.url}` : "";
1992
+ el.textContent =
1993
+ `⚠ Google requires account verification for alias \"${alias}\".\n` +
1994
+ `Complete the browser verification flow, then click Authorize again.${url}`;
1995
+ el.style.display = "block";
1996
+ return;
1997
+ }
1998
+ el.style.display = "none";
1999
+ el.textContent = "";
2000
+ }
2001
+
2002
+ function listAntigravityProviderKeysByAlias(aliasRaw) {
2003
+ const alias = textOf(aliasRaw || "").trim().toLowerCase();
2004
+ if (!alias) return [];
2005
+ const list = Array.isArray(UI.quotaProviders) ? UI.quotaProviders : [];
2006
+ const prefix = `antigravity.${alias}.`;
2007
+ const out = [];
2008
+ for (const q of list) {
2009
+ const pk = textOf(q && q.providerKey ? q.providerKey : "").trim();
2010
+ if (!pk) continue;
2011
+ if (pk.toLowerCase().startsWith(prefix)) out.push(pk);
2012
+ }
2013
+ out.sort((a, b) => a.localeCompare(b));
2014
+ return out;
2015
+ }
2016
+
2017
+ async function recoverAntigravityAliasIfAuthVerify(aliasRaw) {
2018
+ if (!UI.adminAuth || !UI.adminAuth.authenticated) {
2019
+ return;
2020
+ }
2021
+ const keys = listAntigravityProviderKeysByAlias(aliasRaw);
2022
+ if (!keys.length) return;
2023
+ const toRecover = [];
2024
+ for (const key of keys) {
2025
+ const q = getQuotaStateByProviderKey(key);
2026
+ if (!q) continue;
2027
+ if (textOf(q.reason || "").trim() === "authVerify") {
2028
+ toRecover.push(key);
2029
+ }
2030
+ }
2031
+ if (!toRecover.length) {
2032
+ return;
2033
+ }
2034
+ for (const key of toRecover) {
2035
+ try {
2036
+ await apiFetch(`/quota/providers/${encodeURIComponent(key)}/recover`, { method: "POST" });
2037
+ } catch {
2038
+ // best-effort
2039
+ }
2040
+ }
2041
+ }
2042
+
1960
2043
  async function refreshCredentials() {
1961
2044
  const body = $("credentialsTbody");
1962
2045
  body.replaceChildren();
1963
2046
  try {
1964
2047
  const items = await apiFetch("/daemon/credentials");
2048
+ // Best-effort: load quota provider state so we can surface upstream auth issues (e.g. Google verify required).
2049
+ try {
2050
+ const quota = await apiFetch("/quota/providers");
2051
+ const quotaProviders = quota && Array.isArray(quota.providers) ? quota.providers : [];
2052
+ UI.quotaProviders = quotaProviders;
2053
+ UI.quotaProvidersUpdatedAt = Date.now();
2054
+ UI.quotaProviderMap = new Map(quotaProviders.map((q) => [textOf(q.providerKey), q]));
2055
+ } catch {
2056
+ // ignore
2057
+ }
2058
+
2059
+ updateOauthAuthIssueHint();
1965
2060
  for (const c of items || []) {
1966
2061
  const tr = document.createElement("tr");
1967
2062
  const exp = c.expiresInSec == null ? "—" : `${c.expiresInSec}s`;
1968
2063
  tr.appendChild(createCell("td", c.kind || "", ""));
1969
2064
  tr.appendChild(createCell("td", c.provider || "", "mono"));
1970
2065
  tr.appendChild(createCell("td", c.alias || "", "mono"));
1971
- tr.appendChild(createCell("td", c.status || "", ""));
2066
+ const statusTd = document.createElement("td");
2067
+ statusTd.appendChild(pill(c.status || "—", (c.status || "") === "valid" ? "ok" : "warn"));
2068
+ const issue = findAuthIssueForProviderAlias(c.provider, c.alias);
2069
+ if (issue) {
2070
+ statusTd.appendChild(document.createTextNode(" "));
2071
+ statusTd.appendChild(pill("verify required", "bad"));
2072
+ if (issue.url) {
2073
+ statusTd.appendChild(document.createTextNode(" "));
2074
+ const a = document.createElement("a");
2075
+ a.href = issue.url;
2076
+ a.target = "_blank";
2077
+ a.rel = "noreferrer";
2078
+ a.textContent = "open";
2079
+ a.className = "mono";
2080
+ statusTd.appendChild(a);
2081
+ }
2082
+ }
2083
+ tr.appendChild(statusTd);
1972
2084
  tr.appendChild(createCell("td", exp, "mono"));
1973
2085
  tr.appendChild(createCell("td", c.secretRef || "—", "mono"));
1974
2086
  body.appendChild(tr);
@@ -2004,6 +2116,7 @@
2004
2116
  setLog("credentialOpLog", "");
2005
2117
  const provider = $("oauthProviderSelect").value;
2006
2118
  const alias = ($("oauthAuthAliasInput").value || "default").trim() || "default";
2119
+ updateOauthAuthIssueHint();
2007
2120
  const openBrowser = $("oauthOpenBrowser").checked;
2008
2121
  const forceReauthorize = $("oauthForceReauth").checked;
2009
2122
  try {
@@ -2013,6 +2126,17 @@
2013
2126
  });
2014
2127
  setLog("credentialOpLog", `OK. tokenFile: ${out.tokenFile || "—"}`);
2015
2128
  await refreshCredentials();
2129
+ // If OAuth was blocked by Google account verification and the user just completed it,
2130
+ // attempt to recover the alias back into the pool.
2131
+ if (textOf(provider).trim().toLowerCase() === "antigravity") {
2132
+ try {
2133
+ await recoverAntigravityAliasIfAuthVerify(alias);
2134
+ } catch {}
2135
+ try {
2136
+ await refreshQuota();
2137
+ await refreshCredentials();
2138
+ } catch {}
2139
+ }
2016
2140
  } catch (e) {
2017
2141
  setLog("credentialOpLog", `Authorize failed: ${e.message}`);
2018
2142
  }
@@ -2700,6 +2824,8 @@
2700
2824
  const out = await apiFetch("/quota/providers");
2701
2825
  UI.quotaProviders = Array.isArray(out.providers) ? out.providers : [];
2702
2826
  UI.quotaProvidersUpdatedAt = Date.now();
2827
+ UI.quotaProviderMap = new Map(UI.quotaProviders.map((q) => [textOf(q.providerKey), q]));
2828
+ updateOauthAuthIssueHint();
2703
2829
  renderQuotaProviders();
2704
2830
  } catch (e) {
2705
2831
  if (e && e.status === 401) notifyUnauthorizedOnce("quota");
@@ -2816,10 +2942,25 @@
2816
2942
  const reason = textOf(q.reason || "");
2817
2943
  const reasonKind =
2818
2944
  reason === "ok" ? "ok" :
2945
+ reason === "authVerify" ? "bad" :
2819
2946
  reason === "cooldown" ? "warn" :
2820
2947
  reason === "blacklist" || reason === "fatal" || reason === "quotaDepleted" ? "bad" : "warn";
2821
2948
  const reasonTd = document.createElement("td");
2822
2949
  reasonTd.appendChild(pill(reason || "—", reasonKind));
2950
+ const issue = q && q.authIssue && typeof q.authIssue === "object" ? q.authIssue : null;
2951
+ if (issue && issue.kind === "google_account_verification") {
2952
+ const url = textOf(issue.url || "");
2953
+ if (url) {
2954
+ reasonTd.appendChild(document.createTextNode(" "));
2955
+ const a = document.createElement("a");
2956
+ a.href = url;
2957
+ a.target = "_blank";
2958
+ a.rel = "noreferrer";
2959
+ a.textContent = "verify";
2960
+ a.className = "mono";
2961
+ reasonTd.appendChild(a);
2962
+ }
2963
+ }
2823
2964
  tr.appendChild(reasonTd);
2824
2965
 
2825
2966
  const cooldown = formatEpochWithDelta(q.cooldownUntil);
@@ -3332,6 +3473,8 @@
3332
3473
  $("refreshCredentialsBtn").addEventListener("click", refreshCredentials);
3333
3474
  $("saveOauthBrowserBtn").addEventListener("click", saveSettings);
3334
3475
  $("oauthAuthorizeBtn").addEventListener("click", authorizeOauth);
3476
+ $("oauthProviderSelect").addEventListener("change", updateOauthAuthIssueHint);
3477
+ $("oauthAuthAliasInput").addEventListener("input", updateOauthAuthIssueHint);
3335
3478
 
3336
3479
  $("refreshQuotaBtn").addEventListener("click", refreshQuota);
3337
3480
  $("refreshQuotaSnapshotBtn").addEventListener("click", refreshQuotaSnapshotNow);
@@ -6,6 +6,26 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## 0) 你需要知道的文件/目录/入口
10
+
11
+ ### 关键目录(默认都在 `~/.routecodex/`)
12
+
13
+ - OAuth token:`auth/antigravity-oauth-*.json`
14
+ - Camoufox profile:`camoufox-profiles/rc-gemini.<alias>/...`
15
+ - Camoufox 指纹 env:`camoufox-fp/rc-gemini.<alias>.json`
16
+ - reauth-required 标记:`state/antigravity-reauth-required.json`
17
+ - UA 版本缓存:`state/antigravity-ua-version.json`
18
+
19
+ > 为什么 profileId 是 `rc-gemini.<alias>`:`gemini-cli` 与 `antigravity` 共享同一“指纹家族(gemini)”,同 alias 共用同一套 profile/指纹,避免一个账号跑出两套平台指纹。
20
+
21
+ ### 关键日志/接口
22
+
23
+ - 启动 warmup:`[antigravity:warmup] ...`
24
+ - Quota / pool UI 数据:`GET /quota/providers`(包含 `fpOs/fpArch/fpSuffix/...`)
25
+ - OAuth 本地 portal:OAuth 前会展示 token + 指纹摘要(Camoufox profile + OS/Arch)
26
+
27
+ ---
28
+
9
29
  ## 1) 403:`verify your account`(UA/指纹不匹配导致的重新验证)
10
30
 
11
31
  ### 现象
@@ -19,12 +39,55 @@
19
39
  - Antigravity/Gemini **禁止使用 Linux 指纹**(`linux/*`),发现即要求修复并重新 OAuth。
20
40
  - 每个 alias 使用自己对应的 Camoufox profile 指纹(避免“多个账号互相污染”)。
21
41
 
42
+ ### UA 的原则:版本可更新,OS/arch 不可漂移
43
+
44
+ `User-Agent` header 形态:
45
+
46
+ ```
47
+ antigravity/<version> <os>/<arch>
48
+ ```
49
+
50
+ - `<os>/<arch>` 来自 alias 的 OAuth 指纹(Camoufox);**不能随意改**。
51
+ - `<version>` 允许更新(避免 “This version of Antigravity is no longer supported.”)。
52
+
53
+ RouteCodex 的版本号来源(从高到低):
54
+ 1. `ROUTECODEX_ANTIGRAVITY_USER_AGENT` / `RCC_ANTIGRAVITY_USER_AGENT`(完全覆盖)
55
+ 2. `ROUTECODEX_ANTIGRAVITY_UA_VERSION` / `RCC_ANTIGRAVITY_UA_VERSION`
56
+ 3. 远程拉取(可通过 `ROUTECODEX_ANTIGRAVITY_UA_DISABLE_REMOTE=1` 禁用)
57
+ 4. `~/.routecodex/state/antigravity-ua-version.json`
58
+ 5. 兜底版本(仅保证“有值”,不保证不过期)
59
+
22
60
  ### 修复步骤
23
61
  1. 修复指纹(把 linux 修回 windows/macos):
24
62
  - `routecodex camoufox-fp repair --provider antigravity --all`
25
63
  2. 对被标记需要 reauth 的 alias 重新 OAuth:
26
64
  - `routecodex oauth antigravity-auto <token-selector-or-path>`
27
65
  - 或批量:`routecodex oauth reauth-required`
66
+ 3. 如果 Google 提示需要二次验证(账户风控页),用对应 alias 的 profile 打开浏览器完成验证(“养号/解封”):
67
+ - `rcc camoufox ~/.routecodex/auth/antigravity-oauth-<alias>.json --url '<verify_url>'`
68
+
69
+ > 关键点:必须用 **同一个 alias 的 Camoufox profile** 打开验证页,否则容易造成“验证通过但指纹漂移 → 继续 403”。
70
+
71
+ ---
72
+
73
+ ## 1.1) 多账号并发:为什么会“一个号 403 牵连别的号”
74
+
75
+ ### 现象
76
+ - 多个 Antigravity alias 同时跑(或者同一会话内快速切换 alias)时,某个 alias 触发 `verify your account` 之后,其他 alias 也开始陆续 403。
77
+
78
+ ### 我们确认过的根因(与 Antigravity-Manager 行为对齐)
79
+ - Antigravity/Cloud Code Assist **对同一会话指纹(sessionId)跨账号切换极敏感**:同一个会话如果在短时间内换 OAuth cookie / device profile,极容易触发 Google 风控。
80
+ - Antigravity-Manager 的策略是 **session_id → account 绑定**:同一 session 不随便换账号;账号需要验证就让它失败并提示用户完成验证。
81
+
82
+ ### RouteCodex 的修复策略(现在的默认行为)
83
+ - 当某个 Antigravity alias 命中 **403 verify** 时:
84
+ - Virtual Router 会将该 alias(同 runtimeKey 下的所有模型 key)立即标记为 `auth_verify` 冷却(默认 24h,可通过 OAuth 重新恢复)。
85
+ - 同一次请求的重试阶段,会 **避免命中任何 Antigravity alias**,只尝试非 Antigravity 的兜底池(例如 `gemini-cli`)。
86
+ - 当某个 Antigravity alias 命中 **429** 时:
87
+ - 重试阶段同样 **不在 Antigravity 内换号**,只走非 Antigravity 兜底(避免“429→换号→全家 403”)。
88
+ - 具体“换号”交给后续请求 + Virtual Router 健康/冷却机制(而不是在单次请求里瞬间轮询所有账号)。
89
+
90
+ > 经验:这条策略会让“单次请求”的 fallback 更克制,但能显著降低“多账号一起用 → 全部触发二次验证”的概率。
28
91
 
29
92
  ---
30
93
 
@@ -33,6 +96,29 @@
33
96
  ### 现象
34
97
  - 上游返回 `HTTP 429`,错误体含 `RESOURCE_EXHAUSTED`。
35
98
 
99
+ ### 关键结论(本次事故的“真因”)
100
+ 我们遇到过一种非常典型的形态:
101
+ - **第一次请求 OK**(upstream 在响应里下发了 `thoughtSignature`)
102
+ - **第二次(带工具历史)开始 429**(本地在历史 `functionCall` part 里丢了/没注入 `thoughtSignature`)
103
+
104
+ 而“丢签名”的根因不是 quota,而是 **Antigravity provider 配错了 compat profile**:
105
+ - `chat:gemini` 不会跑 `gemini_cli_request_wrap`(因此不会做历史 `thoughtSignature` 注入)
106
+ - Antigravity 必须用 `chat:gemini-cli`(Cloud Code Assist wrapper 对齐 + signature 注入)
107
+
108
+ 修复点:确保你的用户配置中(`~/.routecodex/config.json`):
109
+
110
+ ```jsonc
111
+ {
112
+ "virtualrouter": {
113
+ "providers": {
114
+ "antigravity": {
115
+ "compatibilityProfile": "chat:gemini-cli"
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
36
122
  ### 说明
37
123
  - 这类 429 可能是“真实配额/容量”或上游策略性拒绝,**不能假设一定是本地形状问题**。
38
124
  - 但在 Antigravity/Gemini 路径上,如果请求形状/历史/工具 schema/签名注入不对齐,也会显著放大 429 的出现概率(尤其是长上下文 + 多工具历史)。
@@ -47,7 +133,19 @@
47
133
  - tool 声明字段对齐(例如 Gemini 最新规范要求 `parameters`)
48
134
  - Antigravity wrapper 语义要求:不把内部调试字段带到上游(版本/buildTime 等只留在本地快照)
49
135
 
50
- > 上述行为有黑盒回归:同一 payload 跑“mock upstream”验证 signature 缓存与注入是否生效。
136
+ ### 如何确认签名注入真的生效(不是“占位”)
137
+
138
+ 建议用 codex-samples 快照做证据链(不看日志猜测):
139
+
140
+ 1. 找到一次成功返回且包含 `thoughtSignature` 的响应快照(`provider-response.json` / SSE 片段中 `candidate.content.parts[].thoughtSignature`)。
141
+ 2. 紧接着找下一次“带工具历史”的请求快照,检查 request body 的 `contents[].parts[].functionCall` 是否带同一个 `thoughtSignature`。
142
+
143
+ > 这个验证不依赖“你是否真的有 quota”;只要 upstream 在某次响应里下发过 signature,后续注入就应当可见。
144
+
145
+ ### 如何确认 compat profile 真的生效
146
+ 用 `--snap` 或 dev 默认快照(`~/.routecodex/codex-samples/...`)检查 pipeline stage8:
147
+ - `chat_process.req.stage8.outbound.compat.json` 里必须出现 `chat:gemini-cli`
148
+ - 如果你看到的是 `chat:gemini`,说明 profile 仍然配错(很容易复发“第一次 OK,第二次 429”)
51
149
 
52
150
  ---
53
151
 
@@ -59,6 +157,23 @@
59
157
  - UA 的 `<os>/<arch>` 与指纹期望一致
60
158
  - 若修过指纹,需要完成 OAuth reauth
61
159
 
160
+ ### Warmup 实际做了什么
161
+
162
+ 实现位置:
163
+ - 检查逻辑:`src/providers/auth/antigravity-warmup.ts:warmupCheckAntigravityAlias()`
164
+ - 启动触发:`src/server/runtime/http-server/index.ts`(初始化阶段遍历 `antigravity.<alias>.*` providerKey)
165
+
166
+ 行为(高层):
167
+ 1. 读取 alias 的 Camoufox 指纹 → 推断期望 suffix(`windows|macos` + `amd64|aarch64`)。
168
+ 2. 解析/生成 `User-Agent`(会强制刷新版本号,避免过旧)。
169
+ 3. 校验 `ua_suffix === expected_suffix`。
170
+ 4. 若检测到 alias 处于 `reauth_required` 状态,则提示 OAuth,并在满足条件时自动清理“陈旧标记”(通过 token 文件 mtime 判断)。
171
+ 5. 如果 warmup 失败且 quota 模块可用,会把该 alias 下的所有 providerKey **blacklist** 一段时间(默认很长,避免运行时误用被 ban)。
172
+
173
+ 可控开关:
174
+ - 禁用 warmup:`ROUTECODEX_ANTIGRAVITY_WARMUP=0` / `RCC_ANTIGRAVITY_WARMUP=0`
175
+ - blacklist 时长:`ROUTECODEX_ANTIGRAVITY_WARMUP_BLACKLIST_MS=<ms>`
176
+
62
177
  ### 日志格式
63
178
  - 启动后会看到:
64
179
  - `[antigravity:warmup] OK ... fp_os=... fp_arch=... expected=... actual=...`
@@ -72,4 +187,3 @@
72
187
  ## 4) 变更范围约束(架构要求)
73
188
  - Hub Pipeline 仍是唯一执行路径;Provider 层只做传输(auth/http/retry/compat hook)。
74
189
  - Antigravity 的 session/signature 逻辑 **不扩散**:最多位于 compat 与 gemini-cli provider 路径。
75
-