surf-cli 2.8.0 → 2.10.0
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/README.md +146 -8
- package/native/abort.cjs +65 -0
- package/native/activity-journal.cjs +55 -0
- package/native/ai-queue.cjs +64 -0
- package/native/aistudio-build.cjs +21 -13
- package/native/aistudio-client.cjs +40 -20
- package/native/browser-lock.cjs +2 -2
- package/native/chatgpt-client.cjs +49 -31
- package/native/cli.cjs +352 -482
- package/native/client-transport.cjs +168 -0
- package/native/do-executor.cjs +68 -510
- package/native/do-parser.cjs +8 -249
- package/native/doctor.cjs +55 -5
- package/native/endpoint.cjs +174 -0
- package/native/file-transfer.cjs +734 -0
- package/native/gemini-client.cjs +156 -71
- package/native/grok-client.cjs +98 -89
- package/native/host-helpers.cjs +43 -26
- package/native/host-sessions.cjs +287 -0
- package/native/host.cjs +998 -620
- package/native/listener.cjs +20 -0
- package/native/mcp-server.cjs +60 -65
- package/native/network-export.cjs +116 -0
- package/native/network-store.cjs +38 -58
- package/native/perplexity-client.cjs +46 -17
- package/native/playbook-authoring.cjs +44 -0
- package/native/playbook-cli.cjs +157 -0
- package/native/playbook-client.cjs +259 -0
- package/native/playbook-receipts.cjs +109 -0
- package/native/playbook-records.cjs +208 -0
- package/native/playbook-runtime.cjs +177 -0
- package/native/playbooks.cjs +235 -0
- package/native/private-state.cjs +156 -0
- package/native/redaction.cjs +104 -0
- package/native/remote-auth.cjs +279 -0
- package/native/remote-transport.cjs +337 -0
- package/native/request-pending.cjs +148 -0
- package/native/socket-path.cjs +1 -1
- package/native/workflow-definition.cjs +368 -0
- package/native/workflow-runtime.cjs +225 -0
- package/package.json +9 -6
- package/playbooks/page/ops/read.json +22 -0
- package/playbooks/page/playbook.json +7 -0
- package/scripts/install-native-host.cjs +36 -5
- package/skills/README.md +11 -5
- package/skills/deep-x-research/SKILL.md +106 -0
- package/skills/surf/SKILL.md +72 -5
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
waitForGenerateResponseFromNetwork,
|
|
34
34
|
waitForResponse,
|
|
35
35
|
} = require("./aistudio-response.cjs");
|
|
36
|
+
const { raceAbort, throwIfAborted } = require("./abort.cjs");
|
|
36
37
|
|
|
37
38
|
const DEFAULT_MODEL = "gemini-3.1-pro-preview";
|
|
38
39
|
|
|
@@ -310,22 +311,27 @@ async function query(options) {
|
|
|
310
311
|
cdpCommand,
|
|
311
312
|
readNetworkEntries,
|
|
312
313
|
log = () => {},
|
|
314
|
+
signal,
|
|
313
315
|
} = options;
|
|
316
|
+
throwIfAborted(signal);
|
|
314
317
|
|
|
318
|
+
const guardedReadNetworkEntries = readNetworkEntries
|
|
319
|
+
? (...args) => raceAbort(() => readNetworkEntries(...args), signal)
|
|
320
|
+
: readNetworkEntries;
|
|
315
321
|
const startTime = Date.now();
|
|
316
322
|
log("Starting AI Studio query");
|
|
317
323
|
|
|
318
324
|
const resolvedModel = normalizeModelString(model) || DEFAULT_MODEL;
|
|
319
325
|
log(`Requested model: ${resolvedModel}`);
|
|
320
326
|
|
|
321
|
-
const { cookies } = await getCookies
|
|
327
|
+
const { cookies } = await raceAbort(getCookies, signal);
|
|
322
328
|
if (!hasRequiredCookies(cookies)) {
|
|
323
329
|
throw new Error("Google login required - sign into Google in Chrome first");
|
|
324
330
|
}
|
|
325
331
|
log(`Got ${cookies.length} cookies`);
|
|
326
332
|
|
|
327
333
|
const createdUrl = buildAiStudioUrl(resolvedModel);
|
|
328
|
-
const tabInfo = await createTab(createdUrl);
|
|
334
|
+
const tabInfo = await raceAbort(() => createTab(createdUrl), signal);
|
|
329
335
|
const { tabId } = tabInfo || {};
|
|
330
336
|
|
|
331
337
|
if (!tabId) {
|
|
@@ -333,21 +339,21 @@ async function query(options) {
|
|
|
333
339
|
}
|
|
334
340
|
log(`Created tab ${tabId}`);
|
|
335
341
|
|
|
336
|
-
const cdp = (expr) => cdpEvaluate(tabId, expr);
|
|
337
|
-
const inputCdp = (method, params) => cdpCommand(tabId, method, params);
|
|
342
|
+
const cdp = (expr) => raceAbort(() => cdpEvaluate(tabId, expr), signal);
|
|
343
|
+
const inputCdp = (method, params) => raceAbort(() => cdpCommand(tabId, method, params), signal);
|
|
338
344
|
|
|
339
345
|
let baselineGenerateEntryIds = new Set();
|
|
340
346
|
|
|
341
347
|
try {
|
|
342
|
-
await waitForPageLoad(cdp);
|
|
348
|
+
await raceAbort(waitForPageLoad(cdp), signal);
|
|
343
349
|
log("Page loaded");
|
|
344
350
|
|
|
345
|
-
await waitForStudioReady(cdp);
|
|
351
|
+
await raceAbort(waitForStudioReady(cdp), signal);
|
|
346
352
|
log("AI Studio ready");
|
|
347
353
|
|
|
348
354
|
if (typeof readNetworkEntries === 'function') {
|
|
349
355
|
try {
|
|
350
|
-
const baselineNetwork = await
|
|
356
|
+
const baselineNetwork = await guardedReadNetworkEntries(tabId);
|
|
351
357
|
const baselineEntries = Array.isArray(baselineNetwork?.entries)
|
|
352
358
|
? baselineNetwork.entries
|
|
353
359
|
: Array.isArray(baselineNetwork?.requests)
|
|
@@ -362,6 +368,7 @@ async function query(options) {
|
|
|
362
368
|
|
|
363
369
|
log(`Network baseline ready (${baselineGenerateEntryIds.size} GenerateContent entries)`);
|
|
364
370
|
} catch (e) {
|
|
371
|
+
if (signal?.aborted) throw e;
|
|
365
372
|
log(`Network baseline failed: ${e.message || e}`);
|
|
366
373
|
}
|
|
367
374
|
}
|
|
@@ -369,6 +376,7 @@ async function query(options) {
|
|
|
369
376
|
try {
|
|
370
377
|
await enableUnformattedMarkdownView(cdp, log);
|
|
371
378
|
} catch (e) {
|
|
379
|
+
if (signal?.aborted) throw e;
|
|
372
380
|
log(`Markdown toggle failed: ${e.message}`);
|
|
373
381
|
}
|
|
374
382
|
|
|
@@ -387,8 +395,8 @@ async function query(options) {
|
|
|
387
395
|
try {
|
|
388
396
|
log(`Runtime model param missing; retrying direct navigation: ${createdUrl}`);
|
|
389
397
|
await inputCdp('Page.navigate', { url: createdUrl });
|
|
390
|
-
await waitForPageLoad(cdp);
|
|
391
|
-
await waitForStudioReady(cdp);
|
|
398
|
+
await raceAbort(waitForPageLoad(cdp), signal);
|
|
399
|
+
await raceAbort(waitForStudioReady(cdp), signal);
|
|
392
400
|
runtimeUrl = await evaluate(cdp, 'location.href');
|
|
393
401
|
runtimeModelParam = await evaluate(cdp, `(() => {
|
|
394
402
|
try {
|
|
@@ -398,6 +406,7 @@ async function query(options) {
|
|
|
398
406
|
}
|
|
399
407
|
})()`);
|
|
400
408
|
} catch (e) {
|
|
409
|
+
if (signal?.aborted) throw e;
|
|
401
410
|
log(`Direct model URL navigation retry failed: ${e.message || e}`);
|
|
402
411
|
}
|
|
403
412
|
}
|
|
@@ -408,7 +417,10 @@ async function query(options) {
|
|
|
408
417
|
log(`Model via UI (no URL param): requested="${resolvedModel}"`);
|
|
409
418
|
}
|
|
410
419
|
|
|
411
|
-
const currentModelInfo = await readCurrentModelInfo(cdp).catch(() =>
|
|
420
|
+
const currentModelInfo = await readCurrentModelInfo(cdp).catch((error) => {
|
|
421
|
+
if (signal?.aborted) throw error;
|
|
422
|
+
return { found: false, label: '', modelId: '' };
|
|
423
|
+
});
|
|
412
424
|
|
|
413
425
|
log(`AI Studio URL: ${runtimeUrl}`);
|
|
414
426
|
if (currentModelInfo?.label) {
|
|
@@ -427,8 +439,9 @@ async function query(options) {
|
|
|
427
439
|
|
|
428
440
|
if (!modelApplied && usedUrlParam) {
|
|
429
441
|
try {
|
|
430
|
-
modelApplied = await waitForModelToApply(cdp, resolvedModel, log);
|
|
442
|
+
modelApplied = await raceAbort(waitForModelToApply(cdp, resolvedModel, log), signal);
|
|
431
443
|
} catch (e) {
|
|
444
|
+
if (signal?.aborted) throw e;
|
|
432
445
|
log(`Model apply wait failed: ${e.message}`);
|
|
433
446
|
}
|
|
434
447
|
}
|
|
@@ -436,19 +449,20 @@ async function query(options) {
|
|
|
436
449
|
if (!modelApplied) {
|
|
437
450
|
try {
|
|
438
451
|
log(`Attempting UI model selection fallback: ${resolvedModel}`);
|
|
439
|
-
await selectModel(cdp, resolvedModel, log);
|
|
440
|
-
modelApplied = await waitForModelToApply(cdp, resolvedModel, log, 10000);
|
|
452
|
+
await raceAbort(selectModel(cdp, resolvedModel, log), signal);
|
|
453
|
+
modelApplied = await raceAbort(waitForModelToApply(cdp, resolvedModel, log, 10000), signal);
|
|
441
454
|
} catch (e) {
|
|
455
|
+
if (signal?.aborted) throw e;
|
|
442
456
|
log(`UI model selection fallback failed: ${e.message}`);
|
|
443
457
|
}
|
|
444
458
|
}
|
|
445
459
|
|
|
446
|
-
await closeModelSelectorIfOpen(cdp, log);
|
|
460
|
+
await raceAbort(closeModelSelectorIfOpen(cdp, log), signal);
|
|
447
461
|
|
|
448
|
-
await typePrompt(cdp, inputCdp, prompt);
|
|
462
|
+
await raceAbort(typePrompt(cdp, inputCdp, prompt), signal);
|
|
449
463
|
log("Prompt typed");
|
|
450
464
|
|
|
451
|
-
await submitPrompt(cdp, inputCdp);
|
|
465
|
+
await raceAbort(submitPrompt(cdp, inputCdp), signal);
|
|
452
466
|
log("Submitted, waiting for response...");
|
|
453
467
|
|
|
454
468
|
let response;
|
|
@@ -457,10 +471,11 @@ async function query(options) {
|
|
|
457
471
|
try {
|
|
458
472
|
const networkResult = await waitForGenerateResponseFromNetwork({
|
|
459
473
|
tabId,
|
|
460
|
-
readNetworkEntries,
|
|
474
|
+
readNetworkEntries: guardedReadNetworkEntries,
|
|
461
475
|
timeoutMs: timeout,
|
|
462
476
|
baselineEntryIds: baselineGenerateEntryIds,
|
|
463
477
|
prompt,
|
|
478
|
+
signal,
|
|
464
479
|
log,
|
|
465
480
|
});
|
|
466
481
|
|
|
@@ -474,13 +489,14 @@ async function query(options) {
|
|
|
474
489
|
`${networkResult.requestId ? ` (request ${networkResult.requestId})` : ''}`
|
|
475
490
|
);
|
|
476
491
|
} catch (networkErr) {
|
|
492
|
+
if (signal?.aborted) throw networkErr;
|
|
477
493
|
log(`Network extraction failed, falling back to DOM: ${networkErr.message || networkErr}`);
|
|
478
494
|
|
|
479
495
|
const remainingTimeoutMs = Math.max(timeout - (Date.now() - startTime), 10000);
|
|
480
|
-
response = await waitForResponse(cdp, remainingTimeoutMs, prompt, log);
|
|
496
|
+
response = await raceAbort(waitForResponse(cdp, remainingTimeoutMs, prompt, log), signal);
|
|
481
497
|
}
|
|
482
498
|
} else {
|
|
483
|
-
response = await waitForResponse(cdp, timeout, prompt, log);
|
|
499
|
+
response = await raceAbort(waitForResponse(cdp, timeout, prompt, log), signal);
|
|
484
500
|
}
|
|
485
501
|
|
|
486
502
|
const thinkingInfo = response.thinkingTime ? ` (thought for ${response.thinkingTime}s)` : '';
|
|
@@ -493,7 +509,11 @@ async function query(options) {
|
|
|
493
509
|
tookMs: Date.now() - startTime,
|
|
494
510
|
};
|
|
495
511
|
} finally {
|
|
496
|
-
|
|
512
|
+
try {
|
|
513
|
+
await closeTab(tabId);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
log(`Failed to close AI Studio tab ${tabId}: ${error?.message || error}`);
|
|
516
|
+
}
|
|
497
517
|
}
|
|
498
518
|
}
|
|
499
519
|
|
package/native/browser-lock.cjs
CHANGED
|
@@ -11,8 +11,8 @@ function sleepSync(ms) {
|
|
|
11
11
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function getBrowserLockDir(
|
|
15
|
-
const hash = crypto.createHash("sha256").update(
|
|
14
|
+
function getBrowserLockDir(endpointKey, tempDir) {
|
|
15
|
+
const hash = crypto.createHash("sha256").update(endpointKey).digest("hex").slice(0, 16);
|
|
16
16
|
return path.join(tempDir, `surf-lock-${hash}`);
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
+
const { abortableDelay, raceAbort, throwIfAborted } = require("./abort.cjs");
|
|
2
3
|
|
|
3
4
|
const CHATGPT_URL = "https://chatgpt.com/";
|
|
4
5
|
|
|
@@ -14,8 +15,8 @@ const SELECTORS = {
|
|
|
14
15
|
cloudflareScript: 'script[src*="/challenge-platform/"]',
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
function delay(ms) {
|
|
18
|
-
return
|
|
18
|
+
function delay(ms, signal) {
|
|
19
|
+
return abortableDelay(ms, signal);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
function buildClickDispatcher() {
|
|
@@ -189,8 +190,10 @@ function isChatGPTResponseComplete(snapshot, stableCycles, stableMs) {
|
|
|
189
190
|
return stableCycles >= 6 && stableMs >= 1200;
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
async function evaluate(cdp, expression) {
|
|
193
|
+
async function evaluate(cdp, expression, signal) {
|
|
194
|
+
throwIfAborted(signal);
|
|
193
195
|
const result = await cdp(expression);
|
|
196
|
+
throwIfAborted(signal);
|
|
194
197
|
if (result.exceptionDetails) {
|
|
195
198
|
const desc = result.exceptionDetails.exception?.description ||
|
|
196
199
|
result.exceptionDetails.text ||
|
|
@@ -203,14 +206,15 @@ async function evaluate(cdp, expression) {
|
|
|
203
206
|
return result.result?.value;
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
async function waitForPageLoad(cdp, timeoutMs = 45000) {
|
|
209
|
+
async function waitForPageLoad(cdp, timeoutMs = 45000, signal) {
|
|
210
|
+
throwIfAborted(signal);
|
|
207
211
|
const deadline = Date.now() + timeoutMs;
|
|
208
212
|
while (Date.now() < deadline) {
|
|
209
213
|
const ready = await evaluate(cdp, "document.readyState");
|
|
210
214
|
if (ready === "complete" || ready === "interactive") {
|
|
211
215
|
return;
|
|
212
216
|
}
|
|
213
|
-
await delay(100);
|
|
217
|
+
await delay(100, signal);
|
|
214
218
|
}
|
|
215
219
|
throw new Error("Page did not load in time");
|
|
216
220
|
}
|
|
@@ -252,7 +256,8 @@ async function checkLoginStatus(cdp) {
|
|
|
252
256
|
return result || { status: 0 };
|
|
253
257
|
}
|
|
254
258
|
|
|
255
|
-
async function waitForPromptReady(cdp, timeoutMs = 30000) {
|
|
259
|
+
async function waitForPromptReady(cdp, timeoutMs = 30000, signal) {
|
|
260
|
+
throwIfAborted(signal);
|
|
256
261
|
const deadline = Date.now() + timeoutMs;
|
|
257
262
|
const selectors = JSON.stringify(SELECTORS.promptTextarea.split(", "));
|
|
258
263
|
while (Date.now() < deadline) {
|
|
@@ -270,7 +275,7 @@ async function waitForPromptReady(cdp, timeoutMs = 30000) {
|
|
|
270
275
|
})()`
|
|
271
276
|
);
|
|
272
277
|
if (found) return true;
|
|
273
|
-
await delay(200);
|
|
278
|
+
await delay(200, signal);
|
|
274
279
|
}
|
|
275
280
|
return false;
|
|
276
281
|
}
|
|
@@ -302,7 +307,8 @@ function resolveChatGPTModelMenuOption(items, desiredModel) {
|
|
|
302
307
|
}) || null;
|
|
303
308
|
}
|
|
304
309
|
|
|
305
|
-
async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
310
|
+
async function selectModel(cdp, desiredModel, timeoutMs = 8000, signal) {
|
|
311
|
+
throwIfAborted(signal);
|
|
306
312
|
const modelButton = await evaluate(
|
|
307
313
|
cdp,
|
|
308
314
|
`(() => {
|
|
@@ -321,7 +327,7 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
321
327
|
if (btn) dispatchClickSequence(btn);
|
|
322
328
|
})()`
|
|
323
329
|
);
|
|
324
|
-
await delay(300);
|
|
330
|
+
await delay(300, signal);
|
|
325
331
|
|
|
326
332
|
const normalizedModel = normalizeChatGPTModelChoice(desiredModel);
|
|
327
333
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -361,7 +367,7 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
361
367
|
if (item) dispatchClickSequence(item);
|
|
362
368
|
})()`
|
|
363
369
|
);
|
|
364
|
-
await delay(200);
|
|
370
|
+
await delay(200, signal);
|
|
365
371
|
return match.label;
|
|
366
372
|
}
|
|
367
373
|
|
|
@@ -379,13 +385,14 @@ async function selectModel(cdp, desiredModel, timeoutMs = 8000) {
|
|
|
379
385
|
);
|
|
380
386
|
}
|
|
381
387
|
|
|
382
|
-
await delay(100);
|
|
388
|
+
await delay(100, signal);
|
|
383
389
|
}
|
|
384
390
|
|
|
385
391
|
throw new Error(`Model not found: ${desiredModel} (timeout)`);
|
|
386
392
|
}
|
|
387
393
|
|
|
388
|
-
async function typePrompt(cdp, inputCdp, prompt) {
|
|
394
|
+
async function typePrompt(cdp, inputCdp, prompt, signal) {
|
|
395
|
+
throwIfAborted(signal);
|
|
389
396
|
const selectors = JSON.stringify(SELECTORS.promptTextarea.split(", "));
|
|
390
397
|
const encodedPrompt = JSON.stringify(prompt);
|
|
391
398
|
const focused = await evaluate(
|
|
@@ -416,7 +423,7 @@ async function typePrompt(cdp, inputCdp, prompt) {
|
|
|
416
423
|
throw new Error("Failed to focus prompt textarea");
|
|
417
424
|
}
|
|
418
425
|
await inputCdp("Input.insertText", { text: prompt });
|
|
419
|
-
await delay(300);
|
|
426
|
+
await delay(300, signal);
|
|
420
427
|
const verified = await evaluate(
|
|
421
428
|
cdp,
|
|
422
429
|
`(() => {
|
|
@@ -449,7 +456,8 @@ async function typePrompt(cdp, inputCdp, prompt) {
|
|
|
449
456
|
}
|
|
450
457
|
}
|
|
451
458
|
|
|
452
|
-
async function clickSend(cdp, inputCdp) {
|
|
459
|
+
async function clickSend(cdp, inputCdp, signal) {
|
|
460
|
+
throwIfAborted(signal);
|
|
453
461
|
const selectors = SELECTORS.sendButton.split(", ");
|
|
454
462
|
const selectorsJson = JSON.stringify(selectors);
|
|
455
463
|
const deadline = Date.now() + 8000;
|
|
@@ -475,7 +483,7 @@ async function clickSend(cdp, inputCdp) {
|
|
|
475
483
|
);
|
|
476
484
|
if (result === "clicked") return true;
|
|
477
485
|
if (result === "missing") break;
|
|
478
|
-
await delay(100);
|
|
486
|
+
await delay(100, signal);
|
|
479
487
|
}
|
|
480
488
|
await inputCdp("Input.dispatchKeyEvent", {
|
|
481
489
|
type: "keyDown",
|
|
@@ -574,8 +582,10 @@ async function waitForResponse(
|
|
|
574
582
|
cdp,
|
|
575
583
|
timeoutMs = 2700000,
|
|
576
584
|
baselineAssistant,
|
|
577
|
-
baselineAssistantCount
|
|
585
|
+
baselineAssistantCount,
|
|
586
|
+
signal
|
|
578
587
|
) {
|
|
588
|
+
throwIfAborted(signal);
|
|
579
589
|
const deadline = Date.now() + timeoutMs;
|
|
580
590
|
let previousText = "";
|
|
581
591
|
let stableCycles = 0;
|
|
@@ -588,7 +598,7 @@ async function waitForResponse(
|
|
|
588
598
|
const snapshot = await readChatGPTResponseSnapshot(cdp);
|
|
589
599
|
|
|
590
600
|
if (!snapshot) {
|
|
591
|
-
await delay(400);
|
|
601
|
+
await delay(400, signal);
|
|
592
602
|
continue;
|
|
593
603
|
}
|
|
594
604
|
|
|
@@ -602,7 +612,7 @@ async function waitForResponse(
|
|
|
602
612
|
);
|
|
603
613
|
|
|
604
614
|
if (!hasNewAssistantContent) {
|
|
605
|
-
await delay(400);
|
|
615
|
+
await delay(400, signal);
|
|
606
616
|
continue;
|
|
607
617
|
}
|
|
608
618
|
|
|
@@ -630,7 +640,7 @@ async function waitForResponse(
|
|
|
630
640
|
};
|
|
631
641
|
}
|
|
632
642
|
|
|
633
|
-
await delay(400);
|
|
643
|
+
await delay(400, signal);
|
|
634
644
|
}
|
|
635
645
|
|
|
636
646
|
throw new Error("Response timeout");
|
|
@@ -648,27 +658,33 @@ async function query(options) {
|
|
|
648
658
|
cdpEvaluate,
|
|
649
659
|
cdpCommand,
|
|
650
660
|
uploadFile,
|
|
661
|
+
beforeSubmit,
|
|
651
662
|
log = () => {},
|
|
663
|
+
signal,
|
|
652
664
|
} = options;
|
|
665
|
+
throwIfAborted(signal);
|
|
666
|
+
const guardedUploadFile = uploadFile
|
|
667
|
+
? (...args) => raceAbort(() => uploadFile(...args), signal)
|
|
668
|
+
: uploadFile;
|
|
653
669
|
const startTime = Date.now();
|
|
654
670
|
log("Starting ChatGPT query");
|
|
655
|
-
const { cookies } = await getCookies
|
|
671
|
+
const { cookies } = await raceAbort(getCookies, signal);
|
|
656
672
|
if (!hasRequiredCookies(cookies)) {
|
|
657
673
|
throw new Error("ChatGPT login required");
|
|
658
674
|
}
|
|
659
675
|
log(`Got ${cookies.length} cookies`);
|
|
660
|
-
const tabInfo = await createTab
|
|
676
|
+
const tabInfo = await raceAbort(createTab, signal);
|
|
661
677
|
const { tabId } = tabInfo;
|
|
662
678
|
if (!tabId) {
|
|
663
679
|
throw new Error("Failed to create ChatGPT tab");
|
|
664
680
|
}
|
|
665
681
|
log(`Created tab ${tabId}`);
|
|
666
682
|
|
|
667
|
-
const cdp = (expr) => cdpEvaluate(tabId, expr);
|
|
668
|
-
const inputCdp = (method, params) => cdpCommand(tabId, method, params);
|
|
683
|
+
const cdp = (expr) => raceAbort(() => cdpEvaluate(tabId, expr), signal);
|
|
684
|
+
const inputCdp = (method, params) => raceAbort(() => cdpCommand(tabId, method, params), signal);
|
|
669
685
|
|
|
670
686
|
try {
|
|
671
|
-
await waitForPageLoad(cdp);
|
|
687
|
+
await waitForPageLoad(cdp, 45000, signal);
|
|
672
688
|
log("Page loaded");
|
|
673
689
|
if (await isCloudflareBlocked(cdp)) {
|
|
674
690
|
throw new Error("Cloudflare challenge detected - complete in browser");
|
|
@@ -685,13 +701,13 @@ async function query(options) {
|
|
|
685
701
|
throw new Error("ChatGPT login required");
|
|
686
702
|
}
|
|
687
703
|
log("Login verified");
|
|
688
|
-
const promptReady = await waitForPromptReady(cdp);
|
|
704
|
+
const promptReady = await waitForPromptReady(cdp, 30000, signal);
|
|
689
705
|
if (!promptReady) {
|
|
690
706
|
throw new Error("Prompt textarea not ready");
|
|
691
707
|
}
|
|
692
708
|
log("Prompt ready");
|
|
693
709
|
if (model) {
|
|
694
|
-
const selectedLabel = await selectModel(cdp, model);
|
|
710
|
+
const selectedLabel = await selectModel(cdp, model, 8000, signal);
|
|
695
711
|
log(`Selected model: ${selectedLabel}`);
|
|
696
712
|
}
|
|
697
713
|
if (file) {
|
|
@@ -701,7 +717,7 @@ async function query(options) {
|
|
|
701
717
|
const files = Array.isArray(file) ? file : [file];
|
|
702
718
|
const absFiles = files.map((filePath) => path.resolve(process.cwd(), filePath));
|
|
703
719
|
log(`Uploading ${absFiles.length} file(s) to ChatGPT...`);
|
|
704
|
-
const uploadResult = await
|
|
720
|
+
const uploadResult = await guardedUploadFile(tabId, absFiles);
|
|
705
721
|
if (uploadResult?.error) {
|
|
706
722
|
throw new Error(`ChatGPT file upload failed: ${uploadResult.error}`);
|
|
707
723
|
}
|
|
@@ -709,18 +725,20 @@ async function query(options) {
|
|
|
709
725
|
throw new Error("ChatGPT file upload failed: upload did not report success");
|
|
710
726
|
}
|
|
711
727
|
log("File uploaded, waiting for ChatGPT attachment processing...");
|
|
712
|
-
await delay(1500);
|
|
728
|
+
await delay(1500, signal);
|
|
713
729
|
}
|
|
714
|
-
await typePrompt(cdp, inputCdp, prompt);
|
|
730
|
+
await typePrompt(cdp, inputCdp, prompt, signal);
|
|
715
731
|
log("Prompt typed");
|
|
716
732
|
const baseline = normalizeResponseSnapshot(await readChatGPTResponseSnapshot(cdp));
|
|
717
|
-
await
|
|
733
|
+
if (beforeSubmit) await raceAbort(beforeSubmit, signal);
|
|
734
|
+
await clickSend(cdp, inputCdp, signal);
|
|
718
735
|
log("Prompt sent, waiting for response...");
|
|
719
736
|
const response = await waitForResponse(
|
|
720
737
|
cdp,
|
|
721
738
|
timeout,
|
|
722
739
|
baseline.latestAssistant,
|
|
723
|
-
baseline.assistantCount
|
|
740
|
+
baseline.assistantCount,
|
|
741
|
+
signal
|
|
724
742
|
);
|
|
725
743
|
log(`Response received (${response.text.length} chars)`);
|
|
726
744
|
return {
|