@jsonstudio/rcc 0.89.682 → 0.89.873
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.
- package/dist/build-info.js +2 -2
- package/dist/cli.js +164 -116
- package/dist/cli.js.map +1 -1
- package/dist/client/anthropic/anthropic-protocol-client.js +42 -1
- package/dist/client/anthropic/anthropic-protocol-client.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +4 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
- package/dist/commands/camoufox-backfill.d.ts +2 -0
- package/dist/commands/camoufox-backfill.js +33 -0
- package/dist/commands/camoufox-backfill.js.map +1 -0
- package/dist/commands/camoufox-fp.d.ts +2 -0
- package/dist/commands/camoufox-fp.js +86 -0
- package/dist/commands/camoufox-fp.js.map +1 -0
- package/dist/commands/oauth.d.ts +2 -0
- package/dist/commands/oauth.js +170 -0
- package/dist/commands/oauth.js.map +1 -0
- package/dist/commands/provider-update.js +439 -2
- package/dist/commands/provider-update.js.map +1 -1
- package/dist/commands/quota-status.d.ts +2 -0
- package/dist/commands/quota-status.js +80 -0
- package/dist/commands/quota-status.js.map +1 -0
- package/dist/commands/token-daemon.js +12 -1
- package/dist/commands/token-daemon.js.map +1 -1
- package/dist/config/provider-v2-loader.d.ts +16 -0
- package/dist/config/provider-v2-loader.js +84 -0
- package/dist/config/provider-v2-loader.js.map +1 -0
- package/dist/config/routecodex-config-loader.js +27 -4
- package/dist/config/routecodex-config-loader.js.map +1 -1
- package/dist/config/system-prompts/codex-cli.txt +1 -0
- package/dist/config/virtual-router-builder.d.ts +9 -0
- package/dist/config/virtual-router-builder.js +34 -0
- package/dist/config/virtual-router-builder.js.map +1 -0
- package/dist/config/virtual-router-types.d.ts +25 -0
- package/dist/config/virtual-router-types.js +30 -0
- package/dist/config/virtual-router-types.js.map +1 -0
- package/dist/manager/index.d.ts +10 -0
- package/dist/manager/index.js +27 -0
- package/dist/manager/index.js.map +1 -0
- package/dist/manager/modules/health/index.d.ts +22 -0
- package/dist/manager/modules/health/index.js +82 -0
- package/dist/manager/modules/health/index.js.map +1 -0
- package/dist/manager/modules/quota/index.d.ts +57 -0
- package/dist/manager/modules/quota/index.js +426 -0
- package/dist/manager/modules/quota/index.js.map +1 -0
- package/dist/manager/modules/routing/index.d.ts +17 -0
- package/dist/manager/modules/routing/index.js +61 -0
- package/dist/manager/modules/routing/index.js.map +1 -0
- package/dist/manager/modules/token/index.d.ts +10 -0
- package/dist/manager/modules/token/index.js +58 -0
- package/dist/manager/modules/token/index.js.map +1 -0
- package/dist/manager/storage/base-store.d.ts +6 -0
- package/dist/manager/storage/base-store.js +2 -0
- package/dist/manager/storage/base-store.js.map +1 -0
- package/dist/manager/storage/file-store.d.ts +25 -0
- package/dist/manager/storage/file-store.js +117 -0
- package/dist/manager/storage/file-store.js.map +1 -0
- package/dist/manager/types.d.ts +9 -0
- package/dist/manager/types.js +2 -0
- package/dist/manager/types.js.map +1 -0
- package/dist/message-center/index.d.ts +5 -0
- package/dist/message-center/index.js +6 -0
- package/dist/message-center/index.js.map +1 -0
- package/dist/message-center/message-center.d.ts +93 -0
- package/dist/message-center/message-center.js +189 -0
- package/dist/message-center/message-center.js.map +1 -0
- package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -0
- package/dist/providers/auth/antigravity-userinfo-helper.js +102 -0
- package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
- package/dist/providers/auth/iflow-cookie-auth.d.ts +27 -0
- package/dist/providers/auth/iflow-cookie-auth.js +209 -0
- package/dist/providers/auth/iflow-cookie-auth.js.map +1 -0
- package/dist/providers/auth/oauth-lifecycle.js +29 -22
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/token-scanner/index.js +16 -1
- package/dist/providers/auth/token-scanner/index.js.map +1 -1
- package/dist/providers/core/config/camoufox-launcher.d.ts +16 -0
- package/dist/providers/core/config/camoufox-launcher.js +314 -0
- package/dist/providers/core/config/camoufox-launcher.js.map +1 -0
- package/dist/providers/core/config/oauth-flows.d.ts +9 -0
- package/dist/providers/core/config/oauth-flows.js +50 -19
- package/dist/providers/core/config/oauth-flows.js.map +1 -1
- package/dist/providers/core/config/provider-oauth-configs.d.ts +6 -0
- package/dist/providers/core/config/provider-oauth-configs.js +12 -0
- package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +26 -3
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/antigravity-quota-client.d.ts +10 -0
- package/dist/providers/core/runtime/antigravity-quota-client.js +88 -0
- package/dist/providers/core/runtime/antigravity-quota-client.js.map +1 -0
- package/dist/providers/core/runtime/base-provider.d.ts +2 -1
- package/dist/providers/core/runtime/base-provider.js +93 -34
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +42 -10
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/http-request-executor.js +24 -0
- package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
- package/dist/providers/core/runtime/http-transport-provider.d.ts +0 -3
- package/dist/providers/core/runtime/http-transport-provider.js +32 -136
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-error-classifier.js +18 -10
- package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
- package/dist/providers/core/runtime/rate-limit-manager.d.ts +6 -0
- package/dist/providers/core/runtime/rate-limit-manager.js +23 -0
- package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
- package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-device-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-hybrid-flow.d.ts +1 -0
- package/dist/providers/core/strategies/oauth-hybrid-flow.js +3 -2
- package/dist/providers/core/strategies/oauth-hybrid-flow.js.map +1 -1
- package/dist/providers/core/utils/http-client.js +43 -1
- package/dist/providers/core/utils/http-client.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.js +4 -4
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +13 -1
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +5 -0
- package/dist/scripts/camoufox/gen-fingerprint-env.py +171 -0
- package/dist/scripts/camoufox/launch-auth.mjs +617 -0
- package/dist/server/runtime/http-server/executor-provider.d.ts +1 -0
- package/dist/server/runtime/http-server/executor-provider.js +26 -0
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/executor-response.d.ts +16 -0
- package/dist/server/runtime/http-server/executor-response.js +164 -0
- package/dist/server/runtime/http-server/executor-response.js.map +1 -0
- package/dist/server/runtime/http-server/index.d.ts +1 -0
- package/dist/server/runtime/http-server/index.js +88 -53
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +5 -19
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.d.ts +2 -0
- package/dist/server/runtime/http-server/routes.js +33 -1
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +1 -0
- package/dist/server/utils/client-connection-state.d.ts +8 -0
- package/dist/server/utils/client-connection-state.js +52 -0
- package/dist/server/utils/client-connection-state.js.map +1 -0
- package/dist/server/utils/request-id-manager.js +21 -3
- package/dist/server/utils/request-id-manager.js.map +1 -1
- package/dist/token-daemon/history-store.d.ts +2 -0
- package/dist/token-daemon/history-store.js +6 -2
- package/dist/token-daemon/history-store.js.map +1 -1
- package/dist/token-daemon/index.js +36 -5
- package/dist/token-daemon/index.js.map +1 -1
- package/dist/token-daemon/leader-lock.d.ts +11 -0
- package/dist/token-daemon/leader-lock.js +79 -0
- package/dist/token-daemon/leader-lock.js.map +1 -0
- package/dist/token-daemon/message-bus-integrator.d.ts +98 -0
- package/dist/token-daemon/message-bus-integrator.js +144 -0
- package/dist/token-daemon/message-bus-integrator.js.map +1 -0
- package/dist/token-daemon/provider-registry.d.ts +22 -0
- package/dist/token-daemon/provider-registry.js +201 -0
- package/dist/token-daemon/provider-registry.js.map +1 -0
- package/dist/token-daemon/token-daemon.d.ts +8 -0
- package/dist/token-daemon/token-daemon.js +196 -11
- package/dist/token-daemon/token-daemon.js.map +1 -1
- package/dist/token-portal/local-token-portal.d.ts +1 -0
- package/dist/token-portal/local-token-portal.js +18 -0
- package/dist/token-portal/local-token-portal.js.map +1 -1
- package/dist/token-portal/render.js +1 -0
- package/dist/token-portal/render.js.map +1 -1
- package/dist/tools/error-log.d.ts +31 -0
- package/dist/tools/error-log.js +117 -0
- package/dist/tools/error-log.js.map +1 -0
- package/dist/tools/stats-request-events.d.ts +2 -0
- package/dist/tools/stats-request-events.js +16 -0
- package/dist/tools/stats-request-events.js.map +1 -0
- package/dist/tools/stats-usage.d.ts +31 -0
- package/dist/tools/stats-usage.js +206 -0
- package/dist/tools/stats-usage.js.map +1 -0
- package/package.json +8 -4
- package/scripts/analyze-codex-error-failures.mjs +109 -0
- package/scripts/camoufox/gen-fingerprint-env.py +171 -0
- package/scripts/camoufox/launch-auth.mjs +617 -0
- package/scripts/classify-codex-samples.mjs +251 -0
- package/scripts/cleanup-codex-error-samples.mjs +88 -0
- package/scripts/compare-codex-rccx.mjs +268 -0
- package/scripts/copy-compat-assets.mjs +18 -0
- package/scripts/install-release.sh +1 -1
- package/scripts/local-replay-openai-response.mjs +1 -2
- package/scripts/pack-mode.mjs +16 -6
- package/scripts/replay-codex-sample.mjs +24 -2
- package/scripts/responses-compare-server.mjs +119 -0
- package/scripts/verify-apply-patch.mjs +28 -17
- package/scripts/verify-codex-error-samples.mjs +99 -0
- package/scripts/verify-e2e-toolcall.mjs +19 -4
- package/scripts/virtual-router-shadow-v2-real.mjs +143 -0
- package/scripts/virtual-router-shadow-v2.mjs +122 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Camoufox OAuth launcher for RouteCodex
|
|
3
|
+
// Usage: node launch-auth.mjs --profile <profileId> --url <oauth_or_portal_url>
|
|
4
|
+
|
|
5
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = { profile: 'default', url: '', autoMode: '', devMode: false };
|
|
12
|
+
const list = argv.slice(2);
|
|
13
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
14
|
+
const key = list[i];
|
|
15
|
+
if (key === '--dev') {
|
|
16
|
+
args.devMode = true;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const val = list[i + 1] ?? '';
|
|
20
|
+
if (key === '--profile' && val) {
|
|
21
|
+
args.profile = String(val);
|
|
22
|
+
i += 1;
|
|
23
|
+
} else if (key === '--url' && val) {
|
|
24
|
+
args.url = String(val);
|
|
25
|
+
i += 1;
|
|
26
|
+
} else if (key === '--auto-mode' && val) {
|
|
27
|
+
args.autoMode = String(val);
|
|
28
|
+
i += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!args.autoMode) {
|
|
32
|
+
const envMode = (process.env.ROUTECODEX_CAMOUFOX_AUTO_MODE || '').trim();
|
|
33
|
+
if (envMode) {
|
|
34
|
+
args.autoMode = envMode;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!args.devMode) {
|
|
38
|
+
const envDev = (process.env.ROUTECODEX_CAMOUFOX_DEV_MODE || '').trim();
|
|
39
|
+
if (envDev && envDev !== '0' && envDev.toLowerCase() !== 'false') {
|
|
40
|
+
args.devMode = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return args;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function stripAnsi(input) {
|
|
47
|
+
return input.replace(/\u001b\[[0-9;]*m/g, '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function getCamoufoxCacheRoot() {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const child = spawn('python3', ['-m', 'camoufox', 'path'], {
|
|
53
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
54
|
+
});
|
|
55
|
+
let out = '';
|
|
56
|
+
child.stdout.on('data', (chunk) => {
|
|
57
|
+
out += String(chunk);
|
|
58
|
+
});
|
|
59
|
+
child.on('error', () => resolve(null));
|
|
60
|
+
child.on('close', () => {
|
|
61
|
+
const cleaned = stripAnsi(out).trim();
|
|
62
|
+
const line = cleaned.split(/\r?\n/).filter((l) => l.trim()).pop() || '';
|
|
63
|
+
resolve(line || null);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function ensureProfileDir(profileId) {
|
|
69
|
+
const root = path.join(os.homedir(), '.routecodex', 'camoufox-profiles');
|
|
70
|
+
const dir = path.join(root, profileId);
|
|
71
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
72
|
+
return dir;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function cleanupExistingCamoufox(profileDir) {
|
|
76
|
+
if (!profileDir) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.log('[camoufox-launch-auth] Ensuring Camoufox profile is clean before launch...');
|
|
80
|
+
const pkillArgs = ['-f', profileDir];
|
|
81
|
+
try {
|
|
82
|
+
spawnSync('pkill', pkillArgs, { stdio: 'ignore' });
|
|
83
|
+
} catch {
|
|
84
|
+
// pkill may not exist; ignore
|
|
85
|
+
}
|
|
86
|
+
const lockNames = ['.parentlock', 'parent.lock', 'lock'];
|
|
87
|
+
for (const name of lockNames) {
|
|
88
|
+
const target = path.join(profileDir, name);
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(target)) {
|
|
91
|
+
fs.rmSync(target, { force: true });
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// ignore lock cleanup failure
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function main() {
|
|
100
|
+
const { profile, url, autoMode, devMode } = parseArgs(process.argv);
|
|
101
|
+
if (!url) {
|
|
102
|
+
console.error('[camoufox-launch-auth] Missing --url');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const profileId = profile || 'default';
|
|
107
|
+
const profileDir = await ensureProfileDir(profileId);
|
|
108
|
+
|
|
109
|
+
const cacheRoot = await getCamoufoxCacheRoot();
|
|
110
|
+
if (!cacheRoot) {
|
|
111
|
+
console.error(
|
|
112
|
+
'[camoufox-launch-auth] Failed to resolve Camoufox cache root via "python3 -m camoufox path"'
|
|
113
|
+
);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const camoufoxBinary = resolveCamoufoxBinary(cacheRoot);
|
|
118
|
+
|
|
119
|
+
if (autoMode && autoMode.trim().toLowerCase() === 'iflow') {
|
|
120
|
+
try {
|
|
121
|
+
await runIflowAutoFlow({ url, profileDir, profileId, camoufoxBinary, devMode });
|
|
122
|
+
process.exit(0);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(
|
|
125
|
+
'[camoufox-launch-auth] Auto iflow auth failed:',
|
|
126
|
+
error instanceof Error ? error.message : String(error)
|
|
127
|
+
);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (autoMode && autoMode.trim().toLowerCase() === 'gemini') {
|
|
134
|
+
try {
|
|
135
|
+
await runGeminiAutoFlow({ url, profileDir, camoufoxBinary, devMode });
|
|
136
|
+
process.exit(0);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(
|
|
139
|
+
'[camoufox-launch-auth] Auto gemini auth failed:',
|
|
140
|
+
error instanceof Error ? error.message : String(error)
|
|
141
|
+
);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (autoMode && autoMode.trim().toLowerCase() === 'antigravity') {
|
|
148
|
+
try {
|
|
149
|
+
await runAntigravityAutoFlow({ url, profileDir, camoufoxBinary, devMode });
|
|
150
|
+
process.exit(0);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(
|
|
153
|
+
'[camoufox-launch-auth] Auto antigravity auth failed:',
|
|
154
|
+
error instanceof Error ? error.message : String(error)
|
|
155
|
+
);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await launchManualCamoufox({ camoufoxBinary, profileDir, url });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main().catch((err) => {
|
|
165
|
+
console.error('[camoufox-launch-auth] Unexpected error:', err instanceof Error ? err.message : String(err));
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
function resolveCamoufoxBinary(cacheRoot) {
|
|
170
|
+
const isMac = process.platform === 'darwin';
|
|
171
|
+
if (isMac) {
|
|
172
|
+
const appPath = path.join(cacheRoot, 'Camoufox.app');
|
|
173
|
+
const macBinary = path.join(appPath, 'Contents', 'MacOS', 'camoufox');
|
|
174
|
+
if (fs.existsSync(macBinary)) {
|
|
175
|
+
return macBinary;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const located = spawnSync('which', ['camoufox'], { encoding: 'utf-8' });
|
|
180
|
+
if (located.status === 0) {
|
|
181
|
+
const resolved = located.stdout.trim();
|
|
182
|
+
if (resolved) {
|
|
183
|
+
return resolved;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// ignore lookup failure
|
|
188
|
+
}
|
|
189
|
+
return 'camoufox';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function launchManualCamoufox({ camoufoxBinary, profileDir, url }) {
|
|
193
|
+
let browserExitCode = 0;
|
|
194
|
+
try {
|
|
195
|
+
const browser = spawn(camoufoxBinary, ['-profile', profileDir, url], {
|
|
196
|
+
detached: true,
|
|
197
|
+
stdio: 'ignore'
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const shutdownBrowser = (signal = 'SIGTERM') => {
|
|
201
|
+
try {
|
|
202
|
+
if (browser.pid) {
|
|
203
|
+
try {
|
|
204
|
+
process.kill(-browser.pid, signal);
|
|
205
|
+
return;
|
|
206
|
+
} catch {
|
|
207
|
+
// process group kill may fail on non-unix systems; fall back to direct kill
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
browser.kill(signal);
|
|
211
|
+
} catch {
|
|
212
|
+
// ignore
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
['SIGTERM', 'SIGINT'].forEach((signal) => {
|
|
217
|
+
process.on(signal, () => {
|
|
218
|
+
shutdownBrowser(signal);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
browserExitCode = await new Promise((resolve) => {
|
|
223
|
+
browser.on('exit', (code) => resolve(code ?? 0));
|
|
224
|
+
browser.on('error', () => resolve(1));
|
|
225
|
+
});
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error(
|
|
228
|
+
'[camoufox-launch-auth] Failed to launch Camoufox:',
|
|
229
|
+
error instanceof Error ? error.message : String(error)
|
|
230
|
+
);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
process.exit(browserExitCode);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function runIflowAutoFlow({ url, profileDir, profileId, camoufoxBinary, devMode }) {
|
|
238
|
+
let firefox;
|
|
239
|
+
try {
|
|
240
|
+
({ firefox } = await import('playwright-core'));
|
|
241
|
+
} catch (error) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`playwright-core is required for auto iflow auth (${error instanceof Error ? error.message : String(error)})`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const headless = !devMode;
|
|
248
|
+
console.log(`[camoufox-launch-auth] Launching Camoufox in ${headless ? 'headless' : 'headed'} mode...`);
|
|
249
|
+
cleanupExistingCamoufox(profileDir);
|
|
250
|
+
const context = await firefox.launchPersistentContext(profileDir, {
|
|
251
|
+
executablePath: camoufoxBinary,
|
|
252
|
+
headless,
|
|
253
|
+
acceptDownloads: false
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
let callbackObserved = false;
|
|
257
|
+
try {
|
|
258
|
+
const page = context.pages()[0] || (await context.newPage());
|
|
259
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
260
|
+
console.log('[camoufox-launch-auth] Portal loaded, auto-clicking continue button...');
|
|
261
|
+
const button = page.locator('#continue-btn');
|
|
262
|
+
await button.waitFor({ timeout: 20000 });
|
|
263
|
+
console.log('[camoufox-launch-auth] Continue button located, preparing to click...');
|
|
264
|
+
const popupPromise = context.waitForEvent('page', { timeout: 10000 }).catch(() => null);
|
|
265
|
+
await button.click();
|
|
266
|
+
console.log('[camoufox-launch-auth] Continue button clicked, waiting for iFlow window...');
|
|
267
|
+
const popup = await popupPromise;
|
|
268
|
+
const iflowPage = popup ?? page;
|
|
269
|
+
const selectors = [
|
|
270
|
+
'span.accountName--ZKlffRBc',
|
|
271
|
+
'span[class^="accountName--"]',
|
|
272
|
+
'span[class*="accountName--"]',
|
|
273
|
+
'.account-item span[class*="account"]',
|
|
274
|
+
'.accountName span'
|
|
275
|
+
];
|
|
276
|
+
const selectorQuery = selectors.join(', ');
|
|
277
|
+
console.log('[camoufox-launch-auth] Waiting for iFlow OAuth URL or account DOM to load...');
|
|
278
|
+
const waitForUrlPromise = iflowPage
|
|
279
|
+
.waitForURL((current) => typeof current === 'string' && current.includes('iflow.cn'), { timeout: 60000 })
|
|
280
|
+
.then(() => 'url');
|
|
281
|
+
const waitForDomPromise = iflowPage
|
|
282
|
+
.waitForSelector(selectorQuery, { timeout: 60000 })
|
|
283
|
+
.then(() => 'dom');
|
|
284
|
+
const raceResult = await Promise.race([waitForUrlPromise, waitForDomPromise]);
|
|
285
|
+
waitForUrlPromise.catch(() => {});
|
|
286
|
+
waitForDomPromise.catch(() => {});
|
|
287
|
+
if (raceResult === 'url') {
|
|
288
|
+
console.log(`[camoufox-launch-auth] iFlow URL detected: ${await iflowPage.url()}`);
|
|
289
|
+
await iflowPage.waitForSelector(selectorQuery, { timeout: 60000 });
|
|
290
|
+
} else {
|
|
291
|
+
console.log('[camoufox-launch-auth] Account DOM detected before URL change, continuing...');
|
|
292
|
+
}
|
|
293
|
+
console.log('[camoufox-launch-auth] iFlow OAuth page detected, selecting account...');
|
|
294
|
+
const account = iflowPage.locator(selectorQuery).first();
|
|
295
|
+
await account.waitFor({ timeout: 60000 });
|
|
296
|
+
const matches = await iflowPage.locator(selectors.join(', ')).count().catch(() => 0);
|
|
297
|
+
console.log(`[camoufox-launch-auth] Account locator candidates: ${matches}`);
|
|
298
|
+
console.log('[camoufox-launch-auth] Account element located, preparing to click...');
|
|
299
|
+
await account.scrollIntoViewIfNeeded().catch(() => {});
|
|
300
|
+
await account.hover({ force: true }).catch(() => {});
|
|
301
|
+
const handle = await account.elementHandle();
|
|
302
|
+
const fallbackClicked = await iflowPage.evaluate((el) => {
|
|
303
|
+
if (!el) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
const log = (...args) => console.log('[camoufox-launch-auth][in-page]', ...args);
|
|
307
|
+
const findClickableAncestor = (node) => {
|
|
308
|
+
let current = node;
|
|
309
|
+
let depth = 0;
|
|
310
|
+
while (current && depth < 6) {
|
|
311
|
+
if (current instanceof HTMLElement) {
|
|
312
|
+
const className = current.className || '';
|
|
313
|
+
if (/account/i.test(className) || current.tagName === 'BUTTON') {
|
|
314
|
+
log('clickable ancestor found', current.className);
|
|
315
|
+
return current;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
current = current.parentElement;
|
|
319
|
+
depth += 1;
|
|
320
|
+
}
|
|
321
|
+
return node instanceof HTMLElement ? node : null;
|
|
322
|
+
};
|
|
323
|
+
const target = findClickableAncestor(el);
|
|
324
|
+
if (!target) {
|
|
325
|
+
log('no clickable ancestor located for account span');
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
log('dispatching events to target element');
|
|
329
|
+
const events = ['mouseenter', 'mouseover', 'mousemove', 'mousedown', 'mouseup', 'click'];
|
|
330
|
+
for (const type of events) {
|
|
331
|
+
target.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
}, handle);
|
|
335
|
+
console.log(
|
|
336
|
+
'[camoufox-launch-auth] Account click evaluation result:',
|
|
337
|
+
fallbackClicked ? 'success' : 'failed'
|
|
338
|
+
);
|
|
339
|
+
if (!fallbackClicked) {
|
|
340
|
+
throw new Error('未能定位到可点击的账号元素');
|
|
341
|
+
}
|
|
342
|
+
console.log('[camoufox-launch-auth] Account clicked, waiting for callback...');
|
|
343
|
+
|
|
344
|
+
const callbackPage = await waitForCallback(context, iflowPage);
|
|
345
|
+
callbackObserved = true;
|
|
346
|
+
await callbackPage.waitForLoadState('load', { timeout: 120000 }).catch(() => {});
|
|
347
|
+
console.log('[camoufox-launch-auth] OAuth callback detected, automation complete.');
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (callbackObserved && isBrowserClosedError(error)) {
|
|
350
|
+
console.warn('[camoufox-launch-auth] Browser closed after callback; continuing.');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
throw error;
|
|
354
|
+
} finally {
|
|
355
|
+
await context.close().catch(() => {});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function runGeminiAutoFlow({ url, profileDir, camoufoxBinary, devMode }) {
|
|
360
|
+
let firefox;
|
|
361
|
+
try {
|
|
362
|
+
({ firefox } = await import('playwright-core'));
|
|
363
|
+
} catch (error) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`playwright-core is required for auto gemini auth (${error instanceof Error ? error.message : String(error)})`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const timeoutMs = Number(process.env.ROUTECODEX_CAMOUFOX_GEMINI_TIMEOUT_MS || 120_000);
|
|
370
|
+
const accountPreference = (process.env.ROUTECODEX_CAMOUFOX_ACCOUNT_TEXT || '').trim();
|
|
371
|
+
cleanupExistingCamoufox(profileDir);
|
|
372
|
+
const context = await firefox.launchPersistentContext(profileDir, {
|
|
373
|
+
executablePath: camoufoxBinary,
|
|
374
|
+
headless: !devMode,
|
|
375
|
+
acceptDownloads: false
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
let callbackObserved = false;
|
|
379
|
+
try {
|
|
380
|
+
const page = context.pages()[0] || (await context.newPage());
|
|
381
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
|
|
382
|
+
console.log('[camoufox-launch-auth] Gemini portal loaded, waiting for account selector (<=120s)...');
|
|
383
|
+
const accountSelector = 'div.pGzURd[jsname="V1ur5d"]';
|
|
384
|
+
const accounts = page.locator(accountSelector);
|
|
385
|
+
await accounts.first().waitFor({ timeout: timeoutMs });
|
|
386
|
+
const totalAccounts = await accounts.count();
|
|
387
|
+
console.log(`[camoufox-launch-auth] Gemini accounts detected: ${totalAccounts}`);
|
|
388
|
+
|
|
389
|
+
let targetAccount = accounts.first();
|
|
390
|
+
if (accountPreference) {
|
|
391
|
+
const preferred = accounts.filter({ hasText: accountPreference });
|
|
392
|
+
if (await preferred.count()) {
|
|
393
|
+
console.log(`[camoufox-launch-auth] Selecting account matching preference "${accountPreference}"`);
|
|
394
|
+
targetAccount = preferred.first();
|
|
395
|
+
} else {
|
|
396
|
+
console.warn(
|
|
397
|
+
`[camoufox-launch-auth] Preferred account text "${accountPreference}" not found; falling back to first account`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
await targetAccount.scrollIntoViewIfNeeded().catch(() => {});
|
|
403
|
+
await targetAccount.hover({ force: true }).catch(() => {});
|
|
404
|
+
const handle = await targetAccount.elementHandle();
|
|
405
|
+
if (!handle) {
|
|
406
|
+
throw new Error('无法定位 Gemini 账号元素用于点击');
|
|
407
|
+
}
|
|
408
|
+
const accountText = await targetAccount.innerText().catch(() => '');
|
|
409
|
+
console.log(`[camoufox-launch-auth] Gemini account element located (${accountText || 'unknown label'}), clicking...`);
|
|
410
|
+
await page.evaluate((el) => {
|
|
411
|
+
const events = ['mouseenter', 'mouseover', 'mousemove', 'mousedown', 'mouseup', 'click'];
|
|
412
|
+
for (const type of events) {
|
|
413
|
+
el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
|
|
414
|
+
}
|
|
415
|
+
}, handle);
|
|
416
|
+
|
|
417
|
+
const confirmSelector = 'div.VfPpkd-RLmnJb';
|
|
418
|
+
const confirmResult = await waitForElementInPages(context, confirmSelector, timeoutMs);
|
|
419
|
+
if (confirmResult) {
|
|
420
|
+
console.log('[camoufox-launch-auth] Confirmation button detected, clicking...');
|
|
421
|
+
try {
|
|
422
|
+
await confirmResult.locator.first().click({ timeout: timeoutMs });
|
|
423
|
+
} catch {
|
|
424
|
+
await confirmResult.page.evaluate((sel) => {
|
|
425
|
+
const el = document.querySelector(sel);
|
|
426
|
+
if (!el) return;
|
|
427
|
+
const events = ['mouseenter', 'mouseover', 'mousemove', 'mousedown', 'mouseup', 'click'];
|
|
428
|
+
for (const type of events) {
|
|
429
|
+
el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
|
|
430
|
+
}
|
|
431
|
+
}, confirmSelector);
|
|
432
|
+
}
|
|
433
|
+
console.log('[camoufox-launch-auth] Confirmation acknowledged, waiting for callback...');
|
|
434
|
+
} else {
|
|
435
|
+
console.log('[camoufox-launch-auth] No confirmation button detected within 120s, continuing...');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const activePage = confirmResult?.page || authPage;
|
|
439
|
+
const callbackPage = await waitForCallback(context, activePage);
|
|
440
|
+
await callbackPage.waitForLoadState('load', { timeout: timeoutMs }).catch(() => {});
|
|
441
|
+
console.log('[camoufox-launch-auth] OAuth callback detected, automation complete.');
|
|
442
|
+
} finally {
|
|
443
|
+
await context.close().catch(() => {});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function runAntigravityAutoFlow({ url, profileDir, camoufoxBinary, devMode }) {
|
|
448
|
+
let firefox;
|
|
449
|
+
try {
|
|
450
|
+
({ firefox } = await import('playwright-core'));
|
|
451
|
+
} catch (error) {
|
|
452
|
+
throw new Error(
|
|
453
|
+
`playwright-core is required for auto antigravity auth (${error instanceof Error ? error.message : String(error)})`
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const timeoutMs = Number(process.env.ROUTECODEX_CAMOUFOX_GEMINI_TIMEOUT_MS || 120_000);
|
|
458
|
+
const accountPreference = (process.env.ROUTECODEX_CAMOUFOX_ACCOUNT_TEXT || '').trim();
|
|
459
|
+
cleanupExistingCamoufox(profileDir);
|
|
460
|
+
const context = await firefox.launchPersistentContext(profileDir, {
|
|
461
|
+
executablePath: camoufoxBinary,
|
|
462
|
+
headless: !devMode,
|
|
463
|
+
acceptDownloads: false
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
let callbackObserved = false;
|
|
467
|
+
try {
|
|
468
|
+
const page = context.pages()[0] || (await context.newPage());
|
|
469
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: timeoutMs });
|
|
470
|
+
|
|
471
|
+
let authPage = page;
|
|
472
|
+
if (page.url().includes('token-auth')) {
|
|
473
|
+
console.log('[camoufox-launch-auth] Portal detected, auto-clicking continue button...');
|
|
474
|
+
const button = page.locator('#continue-btn');
|
|
475
|
+
await button.waitFor({ timeout: 20000 });
|
|
476
|
+
const popupPromise = context.waitForEvent('page', { timeout: 10000 }).catch(() => null);
|
|
477
|
+
await button.click();
|
|
478
|
+
const popup = await popupPromise;
|
|
479
|
+
authPage = popup ?? page;
|
|
480
|
+
await authPage.waitForLoadState('domcontentloaded', { timeout: 30000 }).catch(() => {});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
console.log('[camoufox-launch-auth] Antigravity OAuth page loaded, waiting for account selector (<=120s)...');
|
|
484
|
+
const accountSelector = 'div.yAlK0b[jsname="bQIQze"]';
|
|
485
|
+
const accounts = authPage.locator(accountSelector);
|
|
486
|
+
await accounts.first().waitFor({ timeout: timeoutMs });
|
|
487
|
+
const totalAccounts = await accounts.count();
|
|
488
|
+
console.log(`[camoufox-launch-auth] Antigravity accounts detected: ${totalAccounts}`);
|
|
489
|
+
|
|
490
|
+
let targetAccount = accounts.first();
|
|
491
|
+
if (accountPreference) {
|
|
492
|
+
const preferred = accounts.filter({ hasText: accountPreference });
|
|
493
|
+
if (await preferred.count()) {
|
|
494
|
+
console.log(`[camoufox-launch-auth] Selecting account matching preference "${accountPreference}"`);
|
|
495
|
+
targetAccount = preferred.first();
|
|
496
|
+
} else {
|
|
497
|
+
console.warn(
|
|
498
|
+
`[camoufox-launch-auth] Preferred account text "${accountPreference}" not found; falling back to first account`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
await targetAccount.scrollIntoViewIfNeeded().catch(() => {});
|
|
504
|
+
await targetAccount.hover({ force: true }).catch(() => {});
|
|
505
|
+
const handle = await targetAccount.elementHandle();
|
|
506
|
+
if (!handle) {
|
|
507
|
+
throw new Error('无法定位 Antigravity 账号元素用于点击');
|
|
508
|
+
}
|
|
509
|
+
const accountText = await targetAccount.innerText().catch(() => '');
|
|
510
|
+
console.log(
|
|
511
|
+
`[camoufox-launch-auth] Antigravity account element located (${accountText || 'unknown label'}), clicking...`
|
|
512
|
+
);
|
|
513
|
+
await authPage.evaluate((el) => {
|
|
514
|
+
const events = ['mouseenter', 'mouseover', 'mousemove', 'mousedown', 'mouseup', 'click'];
|
|
515
|
+
for (const type of events) {
|
|
516
|
+
el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
|
|
517
|
+
}
|
|
518
|
+
}, handle);
|
|
519
|
+
|
|
520
|
+
const confirmSelector = 'span.VfPpkd-vQzf8d[jsname="V67aGc"]';
|
|
521
|
+
const confirmResult = await waitForElementInPages(context, confirmSelector, timeoutMs);
|
|
522
|
+
if (confirmResult) {
|
|
523
|
+
const signIn = confirmResult.locator.filter({ hasText: 'Sign in' });
|
|
524
|
+
if ((await signIn.count().catch(() => 0)) > 0) {
|
|
525
|
+
console.log('[camoufox-launch-auth] Sign-in confirmation span located (jsname=V67aGc), clicking...');
|
|
526
|
+
await signIn.first().click({ timeout: timeoutMs }).catch(() => {});
|
|
527
|
+
console.log('[camoufox-launch-auth] Antigravity confirmation acknowledged, waiting for callback...');
|
|
528
|
+
} else {
|
|
529
|
+
console.warn('[camoufox-launch-auth] Confirmation element present but text mismatch; skipping auto-click.');
|
|
530
|
+
}
|
|
531
|
+
} else {
|
|
532
|
+
console.log('[camoufox-launch-auth] No Antigravity confirmation button detected within 120s, continuing...');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const activePage = confirmResult?.page || authPage;
|
|
536
|
+
const callbackPage = await waitForCallback(context, activePage);
|
|
537
|
+
callbackObserved = true;
|
|
538
|
+
await callbackPage.waitForLoadState('load', { timeout: timeoutMs }).catch(() => {});
|
|
539
|
+
console.log('[camoufox-launch-auth] OAuth callback detected, automation complete.');
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (callbackObserved && isBrowserClosedError(error)) {
|
|
542
|
+
console.warn('[camoufox-launch-auth] Browser closed after callback; continuing.');
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
throw error;
|
|
546
|
+
} finally {
|
|
547
|
+
await context.close().catch(() => {});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function isBrowserClosedError(error) {
|
|
552
|
+
if (!error) {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
556
|
+
return message.includes('Target page, context or browser has been closed');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function waitForElementInPages(context, selector, timeoutMs) {
|
|
560
|
+
const start = Date.now();
|
|
561
|
+
while (Date.now() - start < timeoutMs) {
|
|
562
|
+
for (const candidate of context.pages()) {
|
|
563
|
+
try {
|
|
564
|
+
const locator = candidate.locator(selector);
|
|
565
|
+
if ((await locator.count()) > 0) {
|
|
566
|
+
return { page: candidate, locator };
|
|
567
|
+
}
|
|
568
|
+
} catch {
|
|
569
|
+
// ignore closed pages
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const elapsed = Date.now() - start;
|
|
573
|
+
const remaining = timeoutMs - elapsed;
|
|
574
|
+
const waitSlice = Math.min(1000, remaining);
|
|
575
|
+
if (waitSlice <= 0) {
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
await context.waitForEvent('page', { timeout: waitSlice });
|
|
580
|
+
} catch {
|
|
581
|
+
await new Promise((resolve) => setTimeout(resolve, waitSlice));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function waitForCallback(context, fallbackPage) {
|
|
588
|
+
const isCallbackUrl = (current) => {
|
|
589
|
+
if (typeof current !== 'string') {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
const lower = current.toLowerCase();
|
|
593
|
+
return (
|
|
594
|
+
lower.startsWith('http://127.0.0.1') ||
|
|
595
|
+
lower.startsWith('http://localhost') ||
|
|
596
|
+
lower.startsWith('https://127.0.0.1')
|
|
597
|
+
);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
const currentPages = context.pages();
|
|
601
|
+
for (const page of currentPages) {
|
|
602
|
+
if (isCallbackUrl(page.url())) {
|
|
603
|
+
return page;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
await fallbackPage.waitForURL((current) => isCallbackUrl(current), { timeout: 120000 });
|
|
609
|
+
return fallbackPage;
|
|
610
|
+
} catch {
|
|
611
|
+
// ignore and wait for popup
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const callback = await context.waitForEvent('page', { timeout: 120000 });
|
|
615
|
+
await callback.waitForLoadState('domcontentloaded', { timeout: 60000 }).catch(() => {});
|
|
616
|
+
return callback;
|
|
617
|
+
}
|