@jsonstudio/rcc 0.89.2239 → 0.90.1

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 (237) hide show
  1. package/README.md +27 -0
  2. package/dist/build-info.js +2 -2
  3. package/dist/build-info.js.map +1 -1
  4. package/dist/cli/commands/claude.js +1 -0
  5. package/dist/cli/commands/claude.js.map +1 -1
  6. package/dist/cli/commands/codex.js +1 -0
  7. package/dist/cli/commands/codex.js.map +1 -1
  8. package/dist/cli/commands/guardian-daemon.d.ts +2 -0
  9. package/dist/cli/commands/guardian-daemon.js +299 -0
  10. package/dist/cli/commands/guardian-daemon.js.map +1 -0
  11. package/dist/cli/commands/init/camoufox.js +1 -1
  12. package/dist/cli/commands/init/camoufox.js.map +1 -1
  13. package/dist/cli/commands/launcher/types.d.ts +6 -0
  14. package/dist/cli/commands/launcher-kernel.js +456 -109
  15. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  16. package/dist/cli/commands/port.js +28 -8
  17. package/dist/cli/commands/port.js.map +1 -1
  18. package/dist/cli/commands/restart.d.ts +4 -0
  19. package/dist/cli/commands/restart.js +91 -42
  20. package/dist/cli/commands/restart.js.map +1 -1
  21. package/dist/cli/commands/start-types.d.ts +4 -0
  22. package/dist/cli/commands/start.js +108 -65
  23. package/dist/cli/commands/start.js.map +1 -1
  24. package/dist/cli/commands/stop.d.ts +3 -0
  25. package/dist/cli/commands/stop.js +30 -63
  26. package/dist/cli/commands/stop.js.map +1 -1
  27. package/dist/cli/config/init-provider-catalog.js +8 -3
  28. package/dist/cli/config/init-provider-catalog.js.map +1 -1
  29. package/dist/cli/guardian/client.d.ts +38 -0
  30. package/dist/cli/guardian/client.js +237 -0
  31. package/dist/cli/guardian/client.js.map +1 -0
  32. package/dist/cli/guardian/paths.d.ts +7 -0
  33. package/dist/cli/guardian/paths.js +13 -0
  34. package/dist/cli/guardian/paths.js.map +1 -0
  35. package/dist/cli/guardian/types.d.ts +30 -0
  36. package/dist/cli/guardian/types.js +2 -0
  37. package/dist/cli/guardian/types.js.map +1 -0
  38. package/dist/cli/register/guardian-daemon-command.d.ts +2 -0
  39. package/dist/cli/register/guardian-daemon-command.js +5 -0
  40. package/dist/cli/register/guardian-daemon-command.js.map +1 -0
  41. package/dist/cli/server/port-utils.js +57 -1
  42. package/dist/cli/server/port-utils.js.map +1 -1
  43. package/dist/cli.js +48 -0
  44. package/dist/cli.js.map +1 -1
  45. package/dist/commands/oauth.js +6 -6
  46. package/dist/commands/oauth.js.map +1 -1
  47. package/dist/config/routecodex-config-loader.js +66 -1
  48. package/dist/config/routecodex-config-loader.js.map +1 -1
  49. package/dist/config/virtual-router-builder.js +18 -0
  50. package/dist/config/virtual-router-builder.js.map +1 -1
  51. package/dist/config/virtual-router-types.js +20 -5
  52. package/dist/config/virtual-router-types.js.map +1 -1
  53. package/dist/daemon-admin-ui/assets/index-C8vP_c5E.js +15 -0
  54. package/dist/daemon-admin-ui/assets/index-DjIoHmNv.css +1 -0
  55. package/dist/daemon-admin-ui/index.html +13 -0
  56. package/dist/docs/daemon-admin-ui.html +328 -57
  57. package/dist/index.d.ts +9 -0
  58. package/dist/index.js +268 -10
  59. package/dist/index.js.map +1 -1
  60. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.d.ts +1 -0
  61. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js +36 -0
  62. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js.map +1 -1
  63. package/dist/manager/modules/quota/provider-quota-daemon.events.js +50 -1
  64. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  65. package/dist/providers/auth/antigravity-user-agent.js +78 -31
  66. package/dist/providers/auth/antigravity-user-agent.js.map +1 -1
  67. package/dist/providers/auth/gemini-cli-userinfo-helper.js +94 -63
  68. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  69. package/dist/providers/auth/iflow-userinfo-helper.js +1 -1
  70. package/dist/providers/auth/iflow-userinfo-helper.js.map +1 -1
  71. package/dist/providers/auth/oauth-error-message.d.ts +1 -0
  72. package/dist/providers/auth/oauth-error-message.js +44 -0
  73. package/dist/providers/auth/oauth-error-message.js.map +1 -0
  74. package/dist/providers/auth/oauth-lifecycle/error-detection.js +42 -8
  75. package/dist/providers/auth/oauth-lifecycle/error-detection.js.map +1 -1
  76. package/dist/providers/auth/oauth-lifecycle/token-io.d.ts +1 -0
  77. package/dist/providers/auth/oauth-lifecycle/token-io.js +12 -0
  78. package/dist/providers/auth/oauth-lifecycle/token-io.js.map +1 -1
  79. package/dist/providers/auth/oauth-lifecycle.js +502 -87
  80. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  81. package/dist/providers/auth/oauth-repair-cooldown.js +2 -7
  82. package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
  83. package/dist/providers/auth/oauth-repair-env.js +3 -5
  84. package/dist/providers/auth/oauth-repair-env.js.map +1 -1
  85. package/dist/providers/auth/oauth-utils/error-extraction.js +42 -8
  86. package/dist/providers/auth/oauth-utils/error-extraction.js.map +1 -1
  87. package/dist/providers/core/config/camoufox-actions.d.ts +31 -0
  88. package/dist/providers/core/config/camoufox-actions.js +461 -0
  89. package/dist/providers/core/config/camoufox-actions.js.map +1 -0
  90. package/dist/providers/core/config/camoufox-launcher.d.ts +3 -0
  91. package/dist/providers/core/config/camoufox-launcher.js +518 -160
  92. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  93. package/dist/providers/core/config/oauth-flows.js +6 -44
  94. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  95. package/dist/providers/core/config/provider-oauth-configs.js +51 -7
  96. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  97. package/dist/providers/core/runtime/provider-error-classifier.js +32 -15
  98. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  99. package/dist/providers/core/runtime/provider-family-profile-utils.js +1 -1
  100. package/dist/providers/core/runtime/provider-family-profile-utils.js.map +1 -1
  101. package/dist/providers/core/runtime/provider-response-postprocessor.js +61 -14
  102. package/dist/providers/core/runtime/provider-response-postprocessor.js.map +1 -1
  103. package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
  104. package/dist/providers/core/strategies/oauth-auth-code-flow.js +124 -19
  105. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  106. package/dist/providers/core/strategies/oauth-device-flow.js +6 -3
  107. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  108. package/dist/providers/profile/families/iflow-profile.js +83 -10
  109. package/dist/providers/profile/families/iflow-profile.js.map +1 -1
  110. package/dist/scripts/camoufox/launch-auth.mjs +112 -5
  111. package/dist/server/handlers/config-admin-handler.js +9 -2
  112. package/dist/server/handlers/config-admin-handler.js.map +1 -1
  113. package/dist/server/handlers/handler-utils.js +3 -12
  114. package/dist/server/handlers/handler-utils.js.map +1 -1
  115. package/dist/server/handlers/logging.js +3 -4
  116. package/dist/server/handlers/logging.js.map +1 -1
  117. package/dist/server/runtime/http-server/clock-client-reaper.js +3 -26
  118. package/dist/server/runtime/http-server/clock-client-reaper.js.map +1 -1
  119. package/dist/server/runtime/http-server/clock-client-registry-utils.d.ts +4 -0
  120. package/dist/server/runtime/http-server/clock-client-registry-utils.js +74 -16
  121. package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +1 -1
  122. package/dist/server/runtime/http-server/clock-client-registry.d.ts +15 -0
  123. package/dist/server/runtime/http-server/clock-client-registry.js +300 -6
  124. package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -1
  125. package/dist/server/runtime/http-server/clock-client-routes.js +49 -19
  126. package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -1
  127. package/dist/server/runtime/http-server/clock-daemon-log-throttle.d.ts +16 -0
  128. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js +49 -0
  129. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +1 -1
  130. package/dist/server/runtime/http-server/clock-scope-resolution.d.ts +14 -0
  131. package/dist/server/runtime/http-server/clock-scope-resolution.js +212 -0
  132. package/dist/server/runtime/http-server/clock-scope-resolution.js.map +1 -0
  133. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +5 -3
  134. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  135. package/dist/server/runtime/http-server/daemon-admin/control-handler.js +104 -15
  136. package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
  137. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +2 -2
  138. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  139. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +24 -0
  140. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +316 -70
  141. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -1
  142. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +190 -1
  143. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  144. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js +18 -29
  145. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js.map +1 -1
  146. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +2 -0
  147. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
  148. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +8 -1
  149. package/dist/server/runtime/http-server/daemon-admin-routes.js +30 -0
  150. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  151. package/dist/server/runtime/http-server/executor/client-injection-flow.d.ts +14 -0
  152. package/dist/server/runtime/http-server/executor/client-injection-flow.js +287 -0
  153. package/dist/server/runtime/http-server/executor/client-injection-flow.js.map +1 -0
  154. package/dist/server/runtime/http-server/executor/index.d.ts +1 -1
  155. package/dist/server/runtime/http-server/executor/index.js +1 -1
  156. package/dist/server/runtime/http-server/executor/index.js.map +1 -1
  157. package/dist/server/runtime/http-server/executor/provider-response-converter.js +236 -62
  158. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  159. package/dist/server/runtime/http-server/executor/request-executor-core-utils.d.ts +1 -0
  160. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js +12 -0
  161. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js.map +1 -1
  162. package/dist/server/runtime/http-server/executor/request-retry-helpers.js +16 -12
  163. package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -1
  164. package/dist/server/runtime/http-server/executor/sse-error-handler.d.ts +1 -0
  165. package/dist/server/runtime/http-server/executor/sse-error-handler.js +13 -2
  166. package/dist/server/runtime/http-server/executor/sse-error-handler.js.map +1 -1
  167. package/dist/server/runtime/http-server/executor/usage-aggregator.d.ts +0 -12
  168. package/dist/server/runtime/http-server/executor/usage-aggregator.js +84 -88
  169. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  170. package/dist/server/runtime/http-server/executor-metadata.js +328 -7
  171. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  172. package/dist/server/runtime/http-server/executor-response.d.ts +1 -0
  173. package/dist/server/runtime/http-server/executor-response.js +52 -58
  174. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  175. package/dist/server/runtime/http-server/http-server-bootstrap.js +50 -6
  176. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
  177. package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +1 -0
  178. package/dist/server/runtime/http-server/http-server-clock-daemon.js +186 -44
  179. package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +1 -1
  180. package/dist/server/runtime/http-server/http-server-lifecycle.js +4 -4
  181. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  182. package/dist/server/runtime/http-server/hub-shadow-compare.js +1 -1
  183. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  184. package/dist/server/runtime/http-server/index.d.ts +1 -0
  185. package/dist/server/runtime/http-server/index.js +1 -0
  186. package/dist/server/runtime/http-server/index.js.map +1 -1
  187. package/dist/server/runtime/http-server/middleware.js +82 -4
  188. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  189. package/dist/server/runtime/http-server/request-executor.js +6 -5
  190. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  191. package/dist/server/runtime/http-server/routes.d.ts +2 -1
  192. package/dist/server/runtime/http-server/routes.js +4 -2
  193. package/dist/server/runtime/http-server/routes.js.map +1 -1
  194. package/dist/server/runtime/http-server/session-dir.js +12 -1
  195. package/dist/server/runtime/http-server/session-dir.js.map +1 -1
  196. package/dist/server/runtime/http-server/stats-manager.d.ts +35 -0
  197. package/dist/server/runtime/http-server/stats-manager.js +269 -21
  198. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  199. package/dist/server/runtime/http-server/stopmessage-scope-rebind.d.ts +13 -0
  200. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js +168 -0
  201. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js.map +1 -0
  202. package/dist/server/runtime/http-server/tmux-session-probe.d.ts +10 -0
  203. package/dist/server/runtime/http-server/tmux-session-probe.js +97 -0
  204. package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -1
  205. package/dist/server-lifecycle/port-utils.d.ts +2 -1
  206. package/dist/server-lifecycle/port-utils.js +84 -4
  207. package/dist/server-lifecycle/port-utils.js.map +1 -1
  208. package/dist/token-daemon/index.d.ts +1 -0
  209. package/dist/token-daemon/index.js +17 -12
  210. package/dist/token-daemon/index.js.map +1 -1
  211. package/dist/utils/clock-client-token.d.ts +2 -1
  212. package/dist/utils/clock-client-token.js +52 -8
  213. package/dist/utils/clock-client-token.js.map +1 -1
  214. package/dist/utils/clock-scope-trace.d.ts +11 -0
  215. package/dist/utils/clock-scope-trace.js +41 -0
  216. package/dist/utils/clock-scope-trace.js.map +1 -0
  217. package/dist/utils/llms-engine-shadow.js +1 -1
  218. package/dist/utils/llms-engine-shadow.js.map +1 -1
  219. package/docs/DAEMON_CONTROL_PLANE.md +1 -0
  220. package/docs/ROUTING_POLICY_SCHEMA.md +4 -2
  221. package/docs/daemon-admin-ui.html +328 -57
  222. package/docs/design/servertool-stopmessage-lifecycle.md +109 -0
  223. package/docs/exec-command-guard-policy.example.v1.json +7 -1
  224. package/docs/providers/antigravity-gemini-provider-compat.md +2 -2
  225. package/package.json +21 -5
  226. package/scripts/build-core.mjs +12 -0
  227. package/scripts/camoufox/launch-auth.mjs +112 -5
  228. package/scripts/ci/repo-sanity.mjs +1 -0
  229. package/scripts/install-global.sh +6 -0
  230. package/scripts/install-verify.mjs +33 -16
  231. package/scripts/run-bg.sh +226 -43
  232. package/scripts/run-fg-gtimeout.sh +158 -14
  233. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +3 -3
  234. package/scripts/tests/ci-jest.mjs +9 -1
  235. package/scripts/triage-errorsamples.mjs +216 -0
  236. package/scripts/verify-codex-error-samples.mjs +92 -15
  237. package/scripts/verify-install-e2e.mjs +57 -27
@@ -5,7 +5,15 @@ import path from 'node:path';
5
5
  import os from 'node:os';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { logOAuthDebug } from '../../auth/oauth-logger.js';
8
+ import { CAMO_CLICK_TARGETS, clickCamoGoogleAccountByHint, hasCamoGoogleSignInPrompt, clickCamoGoogleSignInBySelector, clickCamoTarget, ensureCamoProfile, getActiveCamoPageUrl, gotoCamoUrl, startCamoSession, setDefaultCamoProfile } from './camoufox-actions.js';
8
9
  const activeLaunchers = new Set();
10
+ let lastCamoufoxLaunchFailureReason = null;
11
+ function setCamoufoxLaunchFailureReason(reason) {
12
+ lastCamoufoxLaunchFailureReason = reason && reason.trim().length > 0 ? reason.trim() : null;
13
+ }
14
+ export function getLastCamoufoxLaunchFailureReason() {
15
+ return lastCamoufoxLaunchFailureReason;
16
+ }
9
17
  function registerLauncher(child) {
10
18
  const handle = { child };
11
19
  activeLaunchers.add(handle);
@@ -160,6 +168,47 @@ function getProviderFamily(provider) {
160
168
  }
161
169
  return rawProvider;
162
170
  }
171
+ function isDisabledFlag(value) {
172
+ const raw = String(value || '').trim().toLowerCase();
173
+ return raw === '0' || raw === 'false' || raw === 'no' || raw === 'off';
174
+ }
175
+ export function shouldPreferCamoCliForOAuth(provider) {
176
+ if (isDisabledFlag(process.env.ROUTECODEX_OAUTH_CAMO_CLI) || isDisabledFlag(process.env.RCC_OAUTH_CAMO_CLI)) {
177
+ return false;
178
+ }
179
+ void provider;
180
+ return true;
181
+ }
182
+ function resolveCamoCliCommand() {
183
+ const configured = String(process.env.ROUTECODEX_CAMO_CLI_PATH || process.env.RCC_CAMO_CLI_PATH || '').trim();
184
+ return configured || 'camo';
185
+ }
186
+ function runCamoCliCheck() {
187
+ try {
188
+ const result = spawnSync(resolveCamoCliCommand(), ['--help'], {
189
+ stdio: 'ignore'
190
+ });
191
+ const code = result.error?.code;
192
+ if (code === 'ENOENT') {
193
+ return false;
194
+ }
195
+ return result.status === 0;
196
+ }
197
+ catch {
198
+ return false;
199
+ }
200
+ }
201
+ export function shouldRepairCamoufoxFingerprintForOAuth(providerFamily, navigatorPlatform, hostPlatform = process.platform) {
202
+ const family = String(providerFamily || '').trim().toLowerCase();
203
+ const platform = String(navigatorPlatform || '').trim().toLowerCase();
204
+ if (hostPlatform !== 'darwin') {
205
+ return false;
206
+ }
207
+ if (platform !== 'win32') {
208
+ return false;
209
+ }
210
+ return family === 'gemini' || family === 'iflow' || family === 'qwen';
211
+ }
163
212
  export function sanitizeCamouConfigForOAuth(provider, fingerprintEnv) {
164
213
  const env = { ...(fingerprintEnv || {}) };
165
214
  const raw = env.CAMOU_CONFIG_1;
@@ -180,8 +229,12 @@ export function sanitizeCamouConfigForOAuth(provider, fingerprintEnv) {
180
229
  return env;
181
230
  }
182
231
  const family = getProviderFamily(provider);
183
- if (family === 'iflow' && Object.prototype.hasOwnProperty.call(parsed, 'timezone')) {
184
- delete parsed.timezone;
232
+ if (family === 'iflow') {
233
+ // iFlow OAuth 页面在部分指纹下会因 header 覆盖导致页面乱码;OAuth 场景统一移除该覆盖。
234
+ delete parsed['headers.Accept-Encoding'];
235
+ if (Object.prototype.hasOwnProperty.call(parsed, 'timezone')) {
236
+ delete parsed.timezone;
237
+ }
185
238
  env.CAMOU_CONFIG_1 = JSON.stringify(parsed);
186
239
  }
187
240
  return env;
@@ -317,7 +370,7 @@ function runCamoufoxPathCheck() {
317
370
  return execution.result?.status === 0;
318
371
  }
319
372
  export function isCamoufoxAvailable() {
320
- return runCamoufoxPathCheck();
373
+ return runCamoCliCheck();
321
374
  }
322
375
  function installCamoufox() {
323
376
  const execution = runPythonWithLaunchers((launcher) => [...launcher.argsPrefix, '-m', 'pip', 'install', '--user', '-U', 'camoufox'], { stdio: 'inherit' });
@@ -331,31 +384,11 @@ function installCamoufox() {
331
384
  return execution.result.status === 0;
332
385
  }
333
386
  function ensureCamoufoxInstalled() {
334
- if (runCamoufoxPathCheck()) {
387
+ if (runCamoCliCheck()) {
335
388
  return true;
336
389
  }
337
- const autoInstallRaw = String(process.env.ROUTECODEX_CAMOUFOX_AUTO_INSTALL ||
338
- process.env.RCC_CAMOUFOX_AUTO_INSTALL ||
339
- '1')
340
- .trim()
341
- .toLowerCase();
342
- const autoInstallEnabled = autoInstallRaw !== '0' && autoInstallRaw !== 'false' && autoInstallRaw !== 'no';
343
- if (!autoInstallEnabled) {
344
- logOAuthDebug('[OAuth] Camoufox: auto-install disabled; camoufox not available');
345
- return false;
346
- }
347
- console.warn('[OAuth] Camoufox not detected. Installing via Python launcher ...');
348
- logOAuthDebug('[OAuth] Camoufox: python module not available, attempting install...');
349
- const installed = installCamoufox();
350
- if (!installed) {
351
- logOAuthDebug('[OAuth] Camoufox: auto-install failed or Python launcher unavailable');
352
- return false;
353
- }
354
- const ready = runCamoufoxPathCheck();
355
- if (!ready) {
356
- logOAuthDebug('[OAuth] Camoufox: install completed but camoufox path still unresolved');
357
- }
358
- return ready;
390
+ logOAuthDebug('[OAuth] camo-cli not available');
391
+ return false;
359
392
  }
360
393
  export function ensureCamoufoxInstalledForInit() {
361
394
  return ensureCamoufoxInstalled();
@@ -488,8 +521,9 @@ function ensureFingerprintEnv(profileId, provider, alias) {
488
521
  }
489
522
  const scriptPath = resolveGenFingerprintScriptPath();
490
523
  if (!scriptPath) {
491
- logOAuthDebug('[OAuth] Camoufox: fingerprint generator script not found; falling back to default fingerprint');
492
- return {};
524
+ logOAuthDebug('[OAuth] Camoufox: fingerprint generator script not found; creating minimal fingerprint env');
525
+ const osPolicy = computeOsPolicy(provider, alias);
526
+ return writeMinimalFingerprintEnv(profileId, osPolicy);
493
527
  }
494
528
  const osPolicy = computeOsPolicy(provider, alias);
495
529
  const fpRoot = getFingerprintRoot();
@@ -545,163 +579,487 @@ export function ensureCamoufoxFingerprintForToken(provider, alias) {
545
579
  // fingerprint generation failures are non-fatal for token scanning
546
580
  }
547
581
  }
548
- export async function openAuthInCamoufox(options) {
549
- logOAuthDebug(`[OAuth] Camoufox: launch requested url=${options.url} provider=${options.provider ?? ''} alias=${options.alias ?? ''}`);
550
- if (!ensureCamoufoxInstalled()) {
551
- try {
552
- console.warn('[OAuth] Camoufox not available (Python launcher check failed). Falling back to system browser.');
553
- }
554
- catch {
555
- // ignore
582
+ function isTruthyFlag(value) {
583
+ const raw = String(value || '').trim().toLowerCase();
584
+ return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
585
+ }
586
+ function parsePositiveInt(value, fallbackValue) {
587
+ const parsed = Number.parseInt(String(value || '').trim(), 10);
588
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackValue;
589
+ }
590
+ function isTokenPortalUrl(rawUrl) {
591
+ if (!rawUrl || typeof rawUrl !== 'string') {
592
+ return false;
593
+ }
594
+ try {
595
+ const parsed = new URL(rawUrl);
596
+ const pathName = parsed.pathname || '';
597
+ return pathName === '/token-auth/demo' || pathName.startsWith('/token-auth/demo/');
598
+ }
599
+ catch {
600
+ return rawUrl.includes('/token-auth/demo');
601
+ }
602
+ }
603
+ function resolvePortalOauthUrl(rawUrl) {
604
+ if (!isTokenPortalUrl(rawUrl)) {
605
+ return null;
606
+ }
607
+ try {
608
+ const parsed = new URL(rawUrl);
609
+ const oauthUrl = parsed.searchParams.get('oauthUrl');
610
+ if (!oauthUrl || typeof oauthUrl !== 'string') {
611
+ return null;
556
612
  }
557
- logOAuthDebug('[OAuth] Camoufox: installation missing; falling back to default browser');
613
+ const normalized = oauthUrl.trim();
614
+ return normalized || null;
615
+ }
616
+ catch {
617
+ return null;
618
+ }
619
+ }
620
+ async function maybeAdvanceTokenPortal(options) {
621
+ if (!isTokenPortalUrl(options.launchUrl)) {
622
+ return true;
623
+ }
624
+ const autoMode = String(process.env.ROUTECODEX_CAMOUFOX_AUTO_MODE || '').trim();
625
+ const autoModeEnabled = autoMode.length > 0;
626
+ if (!autoModeEnabled) {
627
+ return true;
628
+ }
629
+ const activeUrl = getActiveCamoPageUrl(options.actionContext);
630
+ if (activeUrl && !isTokenPortalUrl(activeUrl)) {
631
+ logOAuthDebug(`[OAuth] camo-cli portal advance skipped (active page is non-portal): ${activeUrl}`);
632
+ return true;
633
+ }
634
+ if (isTruthyFlag(process.env.ROUTECODEX_CAMOUFOX_OPEN_ONLY) || isTruthyFlag(process.env.RCC_CAMOUFOX_OPEN_ONLY)) {
635
+ logOAuthDebug('[OAuth] camo-cli portal advance skipped (open-only mode)');
636
+ return true;
637
+ }
638
+ const retryCount = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_PORTAL_CLICK_RETRIES, autoModeEnabled ? 8 : 2);
639
+ const retryDelayMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_PORTAL_CLICK_RETRY_DELAY_MS, 350);
640
+ return clickCamoTarget(options.actionContext, CAMO_CLICK_TARGETS.tokenPortalContinue, {
641
+ retries: retryCount,
642
+ retryDelayMs,
643
+ required: autoModeEnabled
644
+ });
645
+ }
646
+ function isIflowOAuthUrl(rawUrl) {
647
+ if (!rawUrl || typeof rawUrl !== 'string') {
558
648
  return false;
559
649
  }
560
- const scriptPath = resolveCamoufoxScriptPath();
561
- if (!scriptPath) {
562
- try {
563
- console.warn('[OAuth] Camoufox launcher script not found. Falling back to system browser.');
650
+ try {
651
+ const parsed = new URL(rawUrl);
652
+ return /(?:^|\.)iflow\.cn$/i.test(parsed.hostname) && parsed.pathname === '/oauth';
653
+ }
654
+ catch {
655
+ return rawUrl.includes('iflow.cn/oauth');
656
+ }
657
+ }
658
+ function isQwenAuthorizeUrl(rawUrl) {
659
+ if (!rawUrl || typeof rawUrl !== 'string') {
660
+ return false;
661
+ }
662
+ try {
663
+ const parsed = new URL(rawUrl);
664
+ return /(?:^|\.)chat\.qwen\.ai$/i.test(parsed.hostname) && parsed.pathname.startsWith('/authorize');
665
+ }
666
+ catch {
667
+ return rawUrl.includes('chat.qwen.ai/authorize');
668
+ }
669
+ }
670
+ function isGoogleAuthUrl(rawUrl) {
671
+ if (!rawUrl || typeof rawUrl !== 'string') {
672
+ return false;
673
+ }
674
+ try {
675
+ const parsed = new URL(rawUrl);
676
+ if (!/(?:^|\.)accounts\.google\.com$/i.test(parsed.hostname)) {
677
+ return false;
564
678
  }
565
- catch {
566
- // ignore
679
+ return parsed.pathname.includes('/signin/') || parsed.pathname.includes('/oauth');
680
+ }
681
+ catch {
682
+ return rawUrl.includes('accounts.google.com');
683
+ }
684
+ }
685
+ async function maybeAdvanceQwenAuthorization(options) {
686
+ const provider = String(options.provider || '').trim().toLowerCase();
687
+ if (provider !== 'qwen') {
688
+ return true;
689
+ }
690
+ let activeUrl = getActiveCamoPageUrl(options.actionContext);
691
+ if (activeUrl && !isQwenAuthorizeUrl(activeUrl) && isTokenPortalUrl(activeUrl)) {
692
+ const settleTimeoutMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_QWEN_OAUTH_SETTLE_MS, 8000);
693
+ const pollIntervalMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_QWEN_OAUTH_POLL_MS, 250);
694
+ const deadline = Date.now() + settleTimeoutMs;
695
+ while (Date.now() <= deadline) {
696
+ const currentUrl = getActiveCamoPageUrl(options.actionContext);
697
+ if (currentUrl) {
698
+ activeUrl = currentUrl;
699
+ if (isQwenAuthorizeUrl(currentUrl) || isGoogleAuthUrl(currentUrl)) {
700
+ break;
701
+ }
702
+ }
703
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
704
+ }
705
+ }
706
+ if (!activeUrl || !isQwenAuthorizeUrl(activeUrl)) {
707
+ return true;
708
+ }
709
+ const autoMode = String(process.env.ROUTECODEX_CAMOUFOX_AUTO_MODE || '').trim().toLowerCase();
710
+ const autoModeEnabled = autoMode === 'qwen';
711
+ if (!autoModeEnabled) {
712
+ return true;
713
+ }
714
+ const retryCount = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_QWEN_CLICK_RETRIES, autoModeEnabled ? 6 : 2);
715
+ const retryDelayMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_QWEN_CLICK_RETRY_DELAY_MS, 350);
716
+ // Try direct confirm first; if not present, fall back to Google entry button.
717
+ await clickCamoTarget(options.actionContext, CAMO_CLICK_TARGETS.qwenAuthorizeConfirm, {
718
+ retries: retryCount,
719
+ retryDelayMs,
720
+ required: false
721
+ });
722
+ await clickCamoTarget(options.actionContext, CAMO_CLICK_TARGETS.qwenGoogleContinue, {
723
+ retries: retryCount,
724
+ retryDelayMs,
725
+ required: false
726
+ });
727
+ return true;
728
+ }
729
+ async function maybeAdvanceIflowAccountSelection(options) {
730
+ const provider = String(options.provider || '').trim().toLowerCase();
731
+ if (provider !== 'iflow') {
732
+ return true;
733
+ }
734
+ const autoMode = String(process.env.ROUTECODEX_CAMOUFOX_AUTO_MODE || '').trim().toLowerCase();
735
+ const autoModeEnabled = autoMode === 'iflow';
736
+ if (!autoModeEnabled) {
737
+ return true;
738
+ }
739
+ let activeUrl = getActiveCamoPageUrl(options.actionContext);
740
+ if (activeUrl && !isIflowOAuthUrl(activeUrl) && isTokenPortalUrl(activeUrl)) {
741
+ const settleTimeoutMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_IFLOW_OAUTH_SETTLE_MS, 7000);
742
+ const pollIntervalMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_IFLOW_OAUTH_POLL_MS, 250);
743
+ const waited = await waitForIflowOAuthPage({
744
+ actionContext: options.actionContext,
745
+ settleTimeoutMs,
746
+ pollIntervalMs
747
+ });
748
+ if (!waited) {
749
+ logOAuthDebug(`[OAuth] camo-cli iflow account advance skipped (iflow oauth page not ready): ${activeUrl || 'n/a'}`);
750
+ return true;
751
+ }
752
+ activeUrl = waited;
753
+ }
754
+ if (!activeUrl || !isIflowOAuthUrl(activeUrl)) {
755
+ return true;
756
+ }
757
+ logOAuthDebug(`[OAuth] camo-cli iflow oauth page ready: ${activeUrl}`);
758
+ const retryCount = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_IFLOW_ACCOUNT_CLICK_RETRIES, autoModeEnabled ? 6 : 2);
759
+ const retryDelayMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_IFLOW_ACCOUNT_CLICK_RETRY_DELAY_MS, 400);
760
+ return clickCamoTarget(options.actionContext, CAMO_CLICK_TARGETS.iflowAccountSelect, {
761
+ retries: retryCount,
762
+ retryDelayMs,
763
+ required: autoModeEnabled
764
+ });
765
+ }
766
+ async function waitForGoogleAuthPage(options) {
767
+ const timeoutMs = options.settleTimeoutMs > 0 ? options.settleTimeoutMs : 7000;
768
+ const intervalMs = options.pollIntervalMs > 0 ? options.pollIntervalMs : 250;
769
+ const deadline = Date.now() + timeoutMs;
770
+ let lastUrl = null;
771
+ while (Date.now() <= deadline) {
772
+ const activeUrl = getActiveCamoPageUrl(options.actionContext);
773
+ if (activeUrl) {
774
+ lastUrl = activeUrl;
775
+ if (isGoogleAuthUrl(activeUrl)) {
776
+ return activeUrl;
777
+ }
778
+ }
779
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
780
+ }
781
+ return isGoogleAuthUrl(lastUrl || '') ? lastUrl : null;
782
+ }
783
+ async function waitForGoogleSignInPrompt(options) {
784
+ const timeoutMs = options.settleTimeoutMs > 0 ? options.settleTimeoutMs : 12_000;
785
+ const intervalMs = options.pollIntervalMs > 0 ? options.pollIntervalMs : 400;
786
+ const deadline = Date.now() + timeoutMs;
787
+ let lastUrl = null;
788
+ while (Date.now() <= deadline) {
789
+ const activeUrl = getActiveCamoPageUrl(options.actionContext);
790
+ if (activeUrl) {
791
+ lastUrl = activeUrl;
792
+ if (!isGoogleAuthUrl(activeUrl)) {
793
+ return { activeUrl, promptDetected: false };
794
+ }
795
+ if (hasCamoGoogleSignInPrompt(options.actionContext)) {
796
+ return { activeUrl, promptDetected: true };
797
+ }
798
+ }
799
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
800
+ }
801
+ return {
802
+ activeUrl: lastUrl,
803
+ promptDetected: !!lastUrl && isGoogleAuthUrl(lastUrl) && hasCamoGoogleSignInPrompt(options.actionContext)
804
+ };
805
+ }
806
+ async function maybeAdvanceGoogleAuth(options) {
807
+ const provider = String(options.provider || '').trim().toLowerCase();
808
+ if (provider !== 'gemini-cli' && provider !== 'antigravity' && provider !== 'qwen') {
809
+ return true;
810
+ }
811
+ const autoMode = String(process.env.ROUTECODEX_CAMOUFOX_AUTO_MODE || '').trim().toLowerCase();
812
+ const autoModeEnabled = autoMode === 'gemini' || autoMode === 'antigravity' || autoMode === 'qwen';
813
+ if (!autoModeEnabled) {
814
+ return true;
815
+ }
816
+ let activeUrl = getActiveCamoPageUrl(options.actionContext);
817
+ const needsGooglePageWait = !!activeUrl && !isGoogleAuthUrl(activeUrl) && (isQwenAuthorizeUrl(activeUrl) || isTokenPortalUrl(activeUrl));
818
+ if (needsGooglePageWait) {
819
+ const settleTimeoutMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_OAUTH_SETTLE_MS, 7000);
820
+ const pollIntervalMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_OAUTH_POLL_MS, 250);
821
+ activeUrl = await waitForGoogleAuthPage({
822
+ actionContext: options.actionContext,
823
+ settleTimeoutMs,
824
+ pollIntervalMs
825
+ });
826
+ }
827
+ if (!activeUrl || !isGoogleAuthUrl(activeUrl)) {
828
+ return true;
829
+ }
830
+ const retryCount = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_CLICK_RETRIES, autoModeEnabled ? 4 : 2);
831
+ const retryDelayMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_CLICK_RETRY_DELAY_MS, 350);
832
+ const signInRetryCount = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_SIGNIN_RETRIES, autoModeEnabled ? 12 : retryCount);
833
+ const signInRetryDelayMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_SIGNIN_RETRY_DELAY_MS, autoModeEnabled ? 500 : retryDelayMs);
834
+ const accountHint = String(process.env.ROUTECODEX_CAMOUFOX_ACCOUNT_TEXT ||
835
+ process.env.RCC_CAMOUFOX_ACCOUNT_TEXT ||
836
+ options.alias ||
837
+ '').trim();
838
+ const hasEmailHint = accountHint.includes('@');
839
+ if (accountHint) {
840
+ const hintClicked = await clickCamoGoogleAccountByHint(options.actionContext, accountHint, {
841
+ retries: retryCount,
842
+ retryDelayMs,
843
+ required: autoModeEnabled && hasEmailHint
844
+ });
845
+ if (!hintClicked) {
846
+ return false;
567
847
  }
568
- logOAuthDebug('[OAuth] Camoufox: launcher script not resolved; falling back to default browser');
848
+ }
849
+ // If we matched a concrete email hint, do not run generic account fallback.
850
+ if (!hasEmailHint) {
851
+ const accountFallbackClicked = await clickCamoTarget(options.actionContext, CAMO_CLICK_TARGETS.googleAccountSelect, {
852
+ retries: retryCount,
853
+ retryDelayMs,
854
+ required: autoModeEnabled
855
+ });
856
+ if (!accountFallbackClicked) {
857
+ return false;
858
+ }
859
+ }
860
+ const signInUrl = getActiveCamoPageUrl(options.actionContext);
861
+ if (!signInUrl) {
862
+ logOAuthDebug('[OAuth] camo-cli google sign-in step failed: active page unavailable (session may be closed)');
569
863
  return false;
570
864
  }
865
+ if (!isGoogleAuthUrl(signInUrl)) {
866
+ logOAuthDebug(`[OAuth] camo-cli google sign-in step skipped (active page is non-google): ${signInUrl || 'n/a'}`);
867
+ return true;
868
+ }
869
+ const strictRequired = provider === 'gemini-cli' || provider === 'antigravity';
870
+ const signInSettleMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_SIGNIN_SETTLE_MS, strictRequired ? 16_000 : 8_000);
871
+ const signInPollMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_GOOGLE_SIGNIN_POLL_MS, 400);
872
+ const signInProbe = await waitForGoogleSignInPrompt({
873
+ actionContext: options.actionContext,
874
+ settleTimeoutMs: signInSettleMs,
875
+ pollIntervalMs: signInPollMs
876
+ });
877
+ if (signInProbe.promptDetected) {
878
+ const signInClicked = await clickCamoGoogleSignInBySelector(options.actionContext, {
879
+ retries: signInRetryCount,
880
+ retryDelayMs: signInRetryDelayMs,
881
+ required: true
882
+ });
883
+ if (!signInClicked) {
884
+ return false;
885
+ }
886
+ }
887
+ else if (strictRequired && signInProbe.activeUrl && isGoogleAuthUrl(signInProbe.activeUrl)) {
888
+ logOAuthDebug(`[OAuth] camo-cli google sign-in prompt not observed on strict provider=${provider} url=${signInProbe.activeUrl}`);
889
+ return false;
890
+ }
891
+ return true;
892
+ }
893
+ async function waitForTokenPortalPage(options) {
894
+ const timeoutMs = options.settleTimeoutMs > 0 ? options.settleTimeoutMs : 4000;
895
+ const intervalMs = options.pollIntervalMs > 0 ? options.pollIntervalMs : 250;
896
+ const deadline = Date.now() + timeoutMs;
897
+ while (Date.now() <= deadline) {
898
+ const activeUrl = getActiveCamoPageUrl(options.actionContext);
899
+ if (activeUrl && isTokenPortalUrl(activeUrl)) {
900
+ return true;
901
+ }
902
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
903
+ }
904
+ return false;
905
+ }
906
+ async function waitForIflowOAuthPage(options) {
907
+ const timeoutMs = options.settleTimeoutMs > 0 ? options.settleTimeoutMs : 7000;
908
+ const intervalMs = options.pollIntervalMs > 0 ? options.pollIntervalMs : 250;
909
+ const deadline = Date.now() + timeoutMs;
910
+ let lastUrl = null;
911
+ while (Date.now() <= deadline) {
912
+ const activeUrl = getActiveCamoPageUrl(options.actionContext);
913
+ if (activeUrl) {
914
+ lastUrl = activeUrl;
915
+ if (isIflowOAuthUrl(activeUrl)) {
916
+ return activeUrl;
917
+ }
918
+ }
919
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
920
+ }
921
+ return isIflowOAuthUrl(lastUrl || '') ? lastUrl : null;
922
+ }
923
+ export async function openAuthInCamoufox(options) {
924
+ setCamoufoxLaunchFailureReason(null);
925
+ logOAuthDebug(`[OAuth] Camoufox: launch requested url=${options.url} provider=${options.provider ?? ''} alias=${options.alias ?? ''}`);
571
926
  const url = options.url;
572
927
  if (!url || typeof url !== 'string') {
573
928
  logOAuthDebug('[OAuth] Camoufox: invalid or empty URL; falling back to default browser');
929
+ setCamoufoxLaunchFailureReason('invalid_launch_url');
574
930
  return false;
575
931
  }
576
932
  const launchUrl = applyGoogleLocaleHint(url);
577
- const profileId = options.profileId && options.profileId.trim().length > 0
933
+ const derivedProfileId = buildProfileId(options.provider, options.alias);
934
+ const explicitProfileId = options.profileId && options.profileId.trim().length > 0
578
935
  ? options.profileId.trim()
579
- : buildProfileId(options.provider, options.alias);
580
- // Ensure profile directory exists ahead of launch so that fingerprint/profile data can be persisted per token.
581
- ensureCamoufoxProfileDir(options.provider, options.alias);
582
- // --- Safety guard: Google OAuth pages must be readable.
583
- // Some Camoufox fingerprints (notably "windows" fonts) can render Google pages as garbled digits
584
- // on macOS hosts. Auto-repair by re-generating the fingerprint using macos policy for this profileId.
585
- try {
586
- const autoRepairRaw = String(process.env.ROUTECODEX_CAMOUFOX_AUTO_REPAIR_OS_MISMATCH ||
587
- process.env.RCC_CAMOUFOX_AUTO_REPAIR_OS_MISMATCH ||
588
- '1')
589
- .trim()
590
- .toLowerCase();
591
- const autoRepairEnabled = autoRepairRaw !== '0' && autoRepairRaw !== 'false' && autoRepairRaw !== 'no';
592
- const family = getProviderFamily(options.provider);
593
- const isGoogleFlow = family === 'gemini';
594
- if (autoRepairEnabled && isGoogleFlow && process.platform === 'darwin') {
595
- const fpPath = getFingerprintPath(profileId);
596
- const existingEnv = loadFingerprintEnv(profileId);
597
- const rawCfg = existingEnv?.CAMOU_CONFIG_1;
598
- if (rawCfg) {
599
- try {
600
- const cfg = JSON.parse(rawCfg);
601
- const platform = typeof cfg['navigator.platform'] === 'string' ? String(cfg['navigator.platform']) : '';
602
- const wantsRepair = platform.toLowerCase() === 'win32';
603
- if (wantsRepair) {
604
- const backupPath = `${fpPath}.bak.${Date.now()}`;
605
- console.warn(`[OAuth] Camoufox fingerprint OS mismatch (host=macos, fp=${platform}). ` +
606
- `Repairing to macos fingerprint for profileId=${profileId} (backup=${backupPath}).`);
607
- try {
608
- fs.renameSync(fpPath, backupPath);
609
- }
610
- catch {
611
- // If backup fails, do not delete/overwrite; just continue with existing fingerprint.
612
- console.warn('[OAuth] Camoufox fingerprint repair skipped (backup failed).');
613
- }
614
- const prevForcedOs = process.env.ROUTECODEX_CAMOUFOX_FORCE_OS;
615
- process.env.ROUTECODEX_CAMOUFOX_FORCE_OS = 'macos';
616
- try {
617
- // Re-generate fingerprint file for this profileId (best-effort).
618
- void ensureFingerprintEnv(profileId, options.provider, options.alias);
619
- }
620
- finally {
621
- if (prevForcedOs === undefined) {
622
- delete process.env.ROUTECODEX_CAMOUFOX_FORCE_OS;
623
- }
624
- else {
625
- process.env.ROUTECODEX_CAMOUFOX_FORCE_OS = prevForcedOs;
626
- }
627
- }
628
- }
629
- }
630
- catch {
631
- // ignore parse errors
632
- }
633
- }
634
- }
936
+ : '';
937
+ const hasProviderAlias = String(options.provider || '').trim().length > 0 || String(options.alias || '').trim().length > 0;
938
+ const profileId = hasProviderAlias
939
+ ? derivedProfileId
940
+ : (explicitProfileId || derivedProfileId);
941
+ if (hasProviderAlias && explicitProfileId && explicitProfileId !== derivedProfileId) {
942
+ logOAuthDebug(`[OAuth] Camoufox profile override ignored: explicit=${explicitProfileId} derived=${derivedProfileId}`);
635
943
  }
636
- catch {
637
- // guardrail must never block OAuth
944
+ // Ensure profile directory exists ahead of launch so that fingerprint/profile data can be persisted per token.
945
+ const profileDir = ensureCamoufoxProfileDir(options.provider, options.alias);
946
+ const profileRoot = getProfileRoot();
947
+ const fingerprintRoot = getFingerprintRoot();
948
+ if (!shouldPreferCamoCliForOAuth(options.provider)) {
949
+ logOAuthDebug('[OAuth] camo-cli launch skipped (provider disabled)');
950
+ setCamoufoxLaunchFailureReason('provider_disabled');
951
+ return false;
638
952
  }
639
- const rawFingerprintEnv = ensureFingerprintEnv(profileId, options.provider, options.alias);
640
- const fingerprintEnv = sanitizeCamouConfigForOAuth(options.provider, rawFingerprintEnv);
641
- const localeEnv = resolveCamoufoxLocaleEnv();
953
+ const devMode = String(process.env.ROUTECODEX_CAMOUFOX_DEV_MODE || '').trim().toLowerCase();
954
+ const headless = !(devMode === '1' || devMode === 'true' || devMode === 'yes' || devMode === 'on');
955
+ const camoCommand = resolveCamoCliCommand();
642
956
  try {
643
- const autoMode = (process.env.ROUTECODEX_CAMOUFOX_AUTO_MODE || '').trim();
644
- const devMode = (process.env.ROUTECODEX_CAMOUFOX_DEV_MODE || '').trim();
645
- console.log(`[OAuth] Camoufox launcher spawn: profileId=${profileId} autoMode=${autoMode || '-'} devMode=${devMode || '0'}`);
646
- logOAuthDebug(`[OAuth] Camoufox: spawning launcher script=${scriptPath} profileId=${profileId}`);
647
- const args = [scriptPath, '--profile', profileId, '--url', launchUrl];
648
- if (autoMode) {
649
- args.push('--auto-mode', autoMode);
650
- }
651
- const child = spawn(process.execPath, args, {
652
- detached: false,
653
- stdio: 'inherit',
654
- env: {
655
- ...process.env,
656
- ...fingerprintEnv,
657
- ...localeEnv,
658
- BROWSER_PROFILE_ID: profileId,
659
- BROWSER_INITIAL_URL: launchUrl
957
+ console.log(`[OAuth] Camoufox launcher spawn via camo-cli: profileId=${profileId} headless=${headless ? '1' : '0'}`);
958
+ const fingerprintEnvRaw = ensureFingerprintEnv(profileId, options.provider, options.alias);
959
+ const fingerprintEnv = sanitizeCamouConfigForOAuth(options.provider, fingerprintEnvRaw);
960
+ const localeEnv = resolveCamoufoxLocaleEnv();
961
+ const sharedEnv = {
962
+ ...process.env,
963
+ ...localeEnv,
964
+ ...fingerprintEnv,
965
+ WEBAUTO_PATHS_PROFILES: profileRoot,
966
+ WEBAUTO_PATHS_FINGERPRINTS: fingerprintRoot,
967
+ BROWSER_PROFILE_ID: profileId,
968
+ BROWSER_PROFILE_DIR: profileDir,
969
+ BROWSER_INITIAL_URL: launchUrl
970
+ };
971
+ const actionContext = {
972
+ camoCommand,
973
+ profileId,
974
+ env: sharedEnv
975
+ };
976
+ if (!ensureCamoProfile(actionContext)) {
977
+ setCamoufoxLaunchFailureReason('profile_create_failed');
978
+ return false;
979
+ }
980
+ // camo currently resolves session routing by default profile in some paths.
981
+ if (!setDefaultCamoProfile(actionContext)) {
982
+ setCamoufoxLaunchFailureReason('profile_default_set_failed');
983
+ return false;
984
+ }
985
+ if (!startCamoSession(actionContext, headless)) {
986
+ setCamoufoxLaunchFailureReason('session_start_failed');
987
+ return false;
988
+ }
989
+ // Ensure we always navigate to the requested URL even when the session already exists.
990
+ let effectiveLaunchUrl = launchUrl;
991
+ let gotoOk = gotoCamoUrl(actionContext, effectiveLaunchUrl);
992
+ if (!gotoOk) {
993
+ const isPortalLaunch = isTokenPortalUrl(launchUrl);
994
+ if (!headless) {
995
+ logOAuthDebug('[OAuth] camo-cli goto returned non-zero in headful mode; keep session for manual portal/open-url continuation');
996
+ gotoOk = true;
660
997
  }
661
- });
662
- registerLauncher(child);
663
- // If the launcher exits immediately with a non-zero code (missing python/camoufox/etc),
664
- // report failure so the caller can fall back to the system browser.
665
- const quickCheckMs = 800;
666
- const ok = await new Promise((resolve) => {
667
- let settled = false;
668
- const settle = (value) => {
669
- if (settled) {
670
- return;
998
+ if (isPortalLaunch) {
999
+ const settleTimeoutMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_PORTAL_GOTO_SETTLE_MS, 4000);
1000
+ const pollIntervalMs = parsePositiveInt(process.env.ROUTECODEX_CAMOUFOX_PORTAL_GOTO_POLL_MS, 250);
1001
+ const portalSettled = await waitForTokenPortalPage({
1002
+ actionContext,
1003
+ settleTimeoutMs,
1004
+ pollIntervalMs
1005
+ });
1006
+ if (portalSettled) {
1007
+ logOAuthDebug('[OAuth] camo-cli portal goto returned non-zero but active page settled to portal; continue with portal flow');
1008
+ gotoOk = true;
671
1009
  }
672
- settled = true;
673
- clearTimeout(timer);
674
- resolve(value);
675
- };
676
- const timer = setTimeout(() => settle(true), quickCheckMs);
677
- if (typeof timer.unref === 'function') {
678
- timer.unref();
679
1010
  }
680
- child.once('error', () => settle(false));
681
- child.once('exit', (code) => {
682
- if (typeof code === 'number' && code !== 0) {
683
- settle(false);
684
- }
685
- else {
686
- settle(true);
1011
+ if (!gotoOk) {
1012
+ const fallbackOauthUrl = resolvePortalOauthUrl(launchUrl);
1013
+ if (fallbackOauthUrl && headless) {
1014
+ logOAuthDebug(`[OAuth] camo-cli portal goto failed; fallback to direct oauthUrl=${fallbackOauthUrl}`);
1015
+ effectiveLaunchUrl = fallbackOauthUrl;
1016
+ gotoOk = gotoCamoUrl(actionContext, effectiveLaunchUrl);
687
1017
  }
688
- });
689
- });
690
- if (!ok) {
691
- try {
692
- console.warn('[OAuth] Camoufox launcher exited early; falling back to system browser.');
693
- }
694
- catch {
695
- // ignore
696
1018
  }
697
1019
  }
698
- // Do not keep the parent process alive solely for the launcher; interactive flows
699
- // will keep running due to the callback server anyway. Cleanup will terminate it.
700
- child.unref();
701
- return ok;
1020
+ if (!gotoOk) {
1021
+ setCamoufoxLaunchFailureReason('goto_failed');
1022
+ return false;
1023
+ }
1024
+ const portalAdvanced = await maybeAdvanceTokenPortal({
1025
+ launchUrl,
1026
+ actionContext
1027
+ });
1028
+ if (!portalAdvanced) {
1029
+ setCamoufoxLaunchFailureReason('element_not_found:token_portal_continue');
1030
+ return false;
1031
+ }
1032
+ const iflowAdvanced = await maybeAdvanceIflowAccountSelection({
1033
+ provider: options.provider,
1034
+ actionContext
1035
+ });
1036
+ if (!iflowAdvanced) {
1037
+ setCamoufoxLaunchFailureReason('element_not_found:iflow_account_select');
1038
+ return false;
1039
+ }
1040
+ const qwenAdvanced = await maybeAdvanceQwenAuthorization({
1041
+ provider: options.provider,
1042
+ actionContext
1043
+ });
1044
+ if (!qwenAdvanced) {
1045
+ setCamoufoxLaunchFailureReason('element_not_found:qwen_authorization');
1046
+ return false;
1047
+ }
1048
+ const googleAdvanced = await maybeAdvanceGoogleAuth({
1049
+ provider: options.provider,
1050
+ alias: options.alias,
1051
+ actionContext
1052
+ });
1053
+ if (!googleAdvanced) {
1054
+ setCamoufoxLaunchFailureReason('element_not_found:google_auth_step');
1055
+ return false;
1056
+ }
1057
+ setCamoufoxLaunchFailureReason(null);
1058
+ return true;
702
1059
  }
703
1060
  catch (error) {
704
- logOAuthDebug(`[OAuth] Camoufox: failed to spawn launcher - ${error instanceof Error ? error.message : String(error)}`);
1061
+ setCamoufoxLaunchFailureReason(`exception:${error instanceof Error ? error.message : String(error)}`);
1062
+ logOAuthDebug(`[OAuth] camo-cli launch failed - ${error instanceof Error ? error.message : String(error)}`);
705
1063
  return false;
706
1064
  }
707
1065
  }