@melaya/runner 1.0.68 → 1.0.70

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.
@@ -143,32 +143,70 @@ export async function runLinkedInLoginFlow(progress) {
143
143
  catch (e) {
144
144
  return { ok: false, error: "playwright_unavailable", detail: e?.message };
145
145
  }
146
- try {
147
- await _installChromium(progress);
148
- }
149
- catch (e) {
150
- return { ok: false, error: "chromium_install_failed", detail: e?.message };
151
- }
152
- // 2. Launch a headed Chromium window. Use a fresh context (no profile
153
- // reuse) so the user always sees a clean LinkedIn login page even
154
- // if they have an old / wrong account session lying around.
146
+ // 2. Launch a headed browser window. Prefer the user's REAL system Chrome
147
+ // (channel:"chrome") — validated 2026-06-22 as the only reliable way to
148
+ // render LinkedIn's login page (the bundled Chromium served a blank /
149
+ // never-painting page; system Chrome renders the form, accepts password
150
+ // + 2FA, and reaches /feed/). System Chrome also needs no 140 MB download.
151
+ // Fall back to bundled Chromium only if Chrome isn't installed.
152
+ // Fresh context (no profile reuse) so the user always sees a clean login.
153
+ //
154
+ // We KEEP the fixed Chrome/127 UA override below: LinkedIn's anti-bot binds
155
+ // cookie issuance to the client fingerprint (UA among others), and every
156
+ // Voyager call in shared/tools/social_linkedin.py sends that exact UA
157
+ // constant. Minting li_at/JSESSIONID under a DIFFERENT UA than the tools
158
+ // later replay triggers a redirect-loop on /voyager/api/me
159
+ // (aiohttp.TooManyRedirects) — verified 2026-06-22 when an interim
160
+ // native-UA attempt broke list_connections / search / resolve_profile /
161
+ // list_conversations while session_status still worked. Keep this string
162
+ // in lock-step with social_linkedin.py USER_AGENT.
155
163
  let browser = null;
156
164
  try {
157
165
  progress("Opening LinkedIn login window…");
158
- browser = await playwright.chromium.launch({
159
- headless: false,
160
- args: [
161
- "--disable-blink-features=AutomationControlled",
162
- ],
163
- });
166
+ try {
167
+ browser = await playwright.chromium.launch({
168
+ headless: false,
169
+ channel: "chrome",
170
+ args: ["--disable-blink-features=AutomationControlled"],
171
+ });
172
+ }
173
+ catch (chromeErr) {
174
+ progress(`System Chrome not available (${(chromeErr?.message || chromeErr).toString().split("\n")[0]}); ` +
175
+ `falling back to the bundled browser.`);
176
+ try {
177
+ await _installChromium(progress);
178
+ }
179
+ catch (e) {
180
+ return { ok: false, error: "chromium_install_failed", detail: e?.message };
181
+ }
182
+ browser = await playwright.chromium.launch({
183
+ headless: false,
184
+ args: ["--disable-blink-features=AutomationControlled"],
185
+ });
186
+ }
164
187
  _activeLoginBrowser = browser;
165
188
  const context = await browser.newContext({
189
+ // MUST stay byte-for-byte equal to shared/tools/social_linkedin.py
190
+ // USER_AGENT (Chrome/127). See the fingerprint-binding note above —
191
+ // a mismatch causes aiohttp.TooManyRedirects on Voyager endpoints.
166
192
  userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
167
193
  "(KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
168
194
  viewport: { width: 1280, height: 820 },
169
195
  });
170
196
  const page = await context.newPage();
171
- await page.goto("https://www.linkedin.com/login", { waitUntil: "domcontentloaded" });
197
+ // LinkedIn's login page no longer fires DOMContentLoaded within 30s (some
198
+ // resource keeps the load pending) — verified 2026-06-18: a `domcontentloaded`
199
+ // wait times out (the title + form DO render, but the event never fires),
200
+ // which aborts the whole flow with login_flow_error. `commit` resolves as
201
+ // soon as the navigation commits (~1.4s); the window keeps loading and the
202
+ // user signs in normally. Wrapped so even a slow commit can't abort — the
203
+ // poll loop below waits for the logged-in URL regardless.
204
+ try {
205
+ await page.goto("https://www.linkedin.com/login", { waitUntil: "commit", timeout: 60000 });
206
+ }
207
+ catch (e) {
208
+ progress(`Login page slow to start (${(e?.message || e).toString().split("\n")[0]}); the window is open — sign in there.`);
209
+ }
172
210
  progress("Sign in with your LinkedIn account in the window that just opened.");
173
211
  // 3. Wait for the user to finish signing in. Two terminal conditions:
174
212
  // (a) URL becomes a logged-in LinkedIn path → success.
@@ -128,13 +128,23 @@ export async function startLumaBrowserBridge(opts) {
128
128
  if (browserLaunching)
129
129
  return browserLaunching;
130
130
  browserLaunching = (async () => {
131
- const b = await playwright.chromium.launch({
132
- headless: true,
133
- args: [
134
- "--disable-blink-features=AutomationControlled",
135
- "--disable-dev-shm-usage",
136
- ],
137
- });
131
+ // Prefer the user's real system Chrome (channel:"chrome"). Its TLS/JA3
132
+ // fingerprint matches the headed login that minted cf_clearance, so the
133
+ // Cloudflare-gated POST /event/register is far more likely to pass — and
134
+ // it skips the 140 MB bundled-Chromium download. Fall back to bundled
135
+ // Chromium when Chrome isn't installed.
136
+ const launchArgs = [
137
+ "--disable-blink-features=AutomationControlled",
138
+ "--disable-dev-shm-usage",
139
+ ];
140
+ let b;
141
+ try {
142
+ b = await playwright.chromium.launch({ headless: true, channel: "chrome", args: launchArgs });
143
+ }
144
+ catch {
145
+ opts.log("Luma browser bridge: system Chrome unavailable, using bundled Chromium.");
146
+ b = await playwright.chromium.launch({ headless: true, args: launchArgs });
147
+ }
138
148
  browser = b;
139
149
  browserLaunching = null;
140
150
  return b;
@@ -168,6 +178,10 @@ export async function startLumaBrowserBridge(opts) {
168
178
  const storageState = await _readStorageState();
169
179
  ctx = await b.newContext({
170
180
  storageState: storageState,
181
+ // MUST stay in lock-step with lumaLogin.ts and shared/tools/luma.py
182
+ // USER_AGENT. cf_clearance is UA-bound: it was minted under this exact
183
+ // string at login, so every replay (here + the direct aiohttp reads)
184
+ // must present the same UA or Cloudflare rejects it with a 403.
171
185
  userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
172
186
  "(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
173
187
  viewport: { width: 1280, height: 820 },
package/dist/lumaLogin.js CHANGED
@@ -112,21 +112,47 @@ export async function runLumaLoginFlow(progress) {
112
112
  catch (e) {
113
113
  return { ok: false, error: "playwright_unavailable", detail: e?.message };
114
114
  }
115
- try {
116
- await _installChromium(progress);
117
- }
118
- catch (e) {
119
- return { ok: false, error: "chromium_install_failed", detail: e?.message };
120
- }
115
+ // Prefer the user's REAL system Chrome (channel:"chrome"). Validated for the
116
+ // LinkedIn login flow 2026-06-22 as the only reliable way to render a headed
117
+ // provider login (bundled Chromium served a blank page). For Luma it's doubly
118
+ // important: Luma is behind Cloudflare Turnstile and only issues cf_clearance
119
+ // (required for POST /event/register) after a challenge passes — a genuine
120
+ // Chrome clears that far more reliably than bundled Chromium, and it needs no
121
+ // 140 MB download. Fall back to bundled Chromium only if Chrome isn't present.
122
+ //
123
+ // We DO keep the fixed UA override below: cf_clearance is bound to the exact
124
+ // UA that minted it, and the replay paths (lumaBrowserBridge.ts and the direct
125
+ // aiohttp reads in shared/tools/luma.py) both send this same fixed string.
126
+ // Turnstile is solved under this overridden UA, so the binding stays internally
127
+ // consistent across mint + every replay. Keep all three in lock-step.
121
128
  let browser = null;
122
129
  try {
123
130
  progress("Opening Luma login window…");
124
- browser = await playwright.chromium.launch({
125
- headless: false,
126
- args: ["--disable-blink-features=AutomationControlled"],
127
- });
131
+ try {
132
+ browser = await playwright.chromium.launch({
133
+ headless: false,
134
+ channel: "chrome",
135
+ args: ["--disable-blink-features=AutomationControlled"],
136
+ });
137
+ }
138
+ catch (chromeErr) {
139
+ progress(`System Chrome not available (${(chromeErr?.message || chromeErr).toString().split("\n")[0]}); ` +
140
+ `falling back to the bundled browser.`);
141
+ try {
142
+ await _installChromium(progress);
143
+ }
144
+ catch (e) {
145
+ return { ok: false, error: "chromium_install_failed", detail: e?.message };
146
+ }
147
+ browser = await playwright.chromium.launch({
148
+ headless: false,
149
+ args: ["--disable-blink-features=AutomationControlled"],
150
+ });
151
+ }
128
152
  _activeLoginBrowser = browser;
129
153
  const context = await browser.newContext({
154
+ // MUST stay in lock-step with shared/tools/luma.py USER_AGENT and
155
+ // lumaBrowserBridge.ts — cf_clearance is UA-bound (see comment above).
130
156
  userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
131
157
  "(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
132
158
  viewport: { width: 1280, height: 820 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -22,15 +22,15 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "dependencies": {
25
- "socket.io-client": "^4.8.0",
26
- "commander": "^12.0.0",
27
25
  "chalk": "^5.3.0",
26
+ "commander": "^12.0.0",
28
27
  "ora": "^8.0.0",
29
- "playwright": "^1.47.0"
28
+ "playwright": "^1.47.0",
29
+ "socket.io-client": "^4.8.0"
30
30
  },
31
31
  "devDependencies": {
32
- "typescript": "^5.5.0",
33
- "@types/node": "^20.0.0"
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.5.0"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=18"