ask-pro 0.1.44 → 0.1.48

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.
@@ -100,6 +100,8 @@ async function waitForAssistantResponseOnce(Runtime, timeoutMs, logger, minTurnI
100
100
  const winner = await Promise.race([raceReadyEvaluation, pollerPromise]);
101
101
  if (winner.kind === "poll") {
102
102
  if (!winner.value) {
103
+ evaluationPromise.catch(() => undefined);
104
+ await terminateRuntimeExecution(Runtime);
103
105
  throw { source: "poll", error: new Error(ASSISTANT_POLL_TIMEOUT_ERROR) };
104
106
  }
105
107
  logger("Captured assistant response via snapshot watchdog");
@@ -120,7 +122,7 @@ async function waitForAssistantResponseOnce(Runtime, timeoutMs, logger, minTurnI
120
122
  if (source === "poll" &&
121
123
  error instanceof Error &&
122
124
  error.message === ASSISTANT_POLL_TIMEOUT_ERROR) {
123
- evaluation = await evaluationPromise;
125
+ throw new Error("Timed out waiting for assistant response");
124
126
  }
125
127
  else if (source === "poll") {
126
128
  pollerAbort.abort();
@@ -385,7 +387,7 @@ async function pollAssistantCompletion(Runtime, timeoutMs, minTurnIndex, expecte
385
387
  async function isStopButtonVisible(Runtime) {
386
388
  try {
387
389
  const { result } = await Runtime.evaluate({
388
- expression: `Boolean(document.querySelector('${STOP_BUTTON_SELECTOR}'))`,
390
+ expression: visibleStopButtonExpression(JSON.stringify(STOP_BUTTON_SELECTOR)),
389
391
  returnByValue: true,
390
392
  });
391
393
  return Boolean(result?.value);
@@ -394,6 +396,13 @@ async function isStopButtonVisible(Runtime) {
394
396
  return false;
395
397
  }
396
398
  }
399
+ function visibleStopButtonExpression(selector) {
400
+ return `Array.from(document.querySelectorAll(${selector})).some((node) => {
401
+ const style = getComputedStyle(node);
402
+ const rect = node.getBoundingClientRect();
403
+ return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none' && style.opacity !== '0';
404
+ })`;
405
+ }
397
406
  async function isCompletionVisible(Runtime) {
398
407
  try {
399
408
  const { result } = await Runtime.evaluate({
@@ -676,7 +685,7 @@ function buildResponseObserverExpression(timeoutMs, minTurnIndex, expectedConver
676
685
  } else {
677
686
  stableCycles += 1;
678
687
  }
679
- const stopVisible = Boolean(document.querySelector(STOP_SELECTOR));
688
+ const stopVisible = ${visibleStopButtonExpression("STOP_SELECTOR")};
680
689
  const finishedVisible = isLastAssistantTurnFinished();
681
690
 
682
691
  if (finishedVisible || (!stopVisible && stableCycles >= stableTarget)) {
@@ -772,7 +781,7 @@ function buildAssistantExtractor(functionName) {
772
781
  button => (button.textContent || '').trim().toLowerCase() === 'stopped thinking',
773
782
  );
774
783
  if (index === turns.length - 1 && stopped && turnText.toLowerCase() === 'stopped thinking' &&
775
- !document.querySelector('${STOP_BUTTON_SELECTOR}')) {
784
+ !(${visibleStopButtonExpression(JSON.stringify(STOP_BUTTON_SELECTOR))})) {
776
785
  return { text: '', stoppedWithoutAnswer: true, turnIndex: index };
777
786
  }
778
787
  expandCollapsibles(messageRoot);
@@ -868,7 +877,7 @@ function buildMarkdownFallbackExtractor(minTurnLiteral) {
868
877
  document.querySelector('[role="main"]'),
869
878
  ].filter(Boolean);
870
879
  if (roots.length === 0) return null;
871
- const markdownSelector = '.markdown,[data-message-content],[data-testid*="message"],.prose,[class*="markdown"]';
880
+ const markdownSelector = 'div[class*="MarkdownRoot-"],.markdown,[data-message-content],[data-testid*="message"],.prose,[class*="markdown"]';
872
881
  const isExcluded = (node) =>
873
882
  Boolean(
874
883
  node?.querySelector?.('[data-streaming-response-status]') || node?.closest?.(
@@ -923,6 +932,7 @@ function buildMarkdownFallbackExtractor(minTurnLiteral) {
923
932
  };
924
933
  const markdowns = Array.from(root.querySelectorAll(markdownSelector))
925
934
  .filter((node) => !isExcluded(node))
935
+ .filter((node) => !node.closest('div[class*="MarkdownRoot-"]') || node.matches('div[class*="MarkdownRoot-"]'))
926
936
  .filter((node) => {
927
937
  const container = node.closest('[data-message-author-role], [data-turn]');
928
938
  if (!container) return true;
@@ -942,6 +952,7 @@ function buildMarkdownFallbackExtractor(minTurnLiteral) {
942
952
  if (!container || container === root || container === document.body) continue;
943
953
  const scoped = Array.from(container.querySelectorAll(markdownSelector))
944
954
  .filter((node) => !isExcluded(node))
955
+ .filter((node) => !node.closest('div[class*="MarkdownRoot-"]') || node.matches('div[class*="MarkdownRoot-"]'))
945
956
  .filter((node) => {
946
957
  const roleNode = node.closest('[data-message-author-role], [data-turn]');
947
958
  if (!roleNode) return true;
@@ -1242,4 +1253,5 @@ function cleanAssistantText(text) {
1242
1253
  export const __test__ = {
1243
1254
  buildResponseObserverExpression,
1244
1255
  recoverAssistantResponse,
1256
+ visibleStopButtonExpression,
1245
1257
  };
@@ -281,6 +281,7 @@ function buildThinkingTimeExpression(level) {
281
281
  const text = normalize(menu.textContent ?? '');
282
282
  const testId = normalize(menu.getAttribute?.('data-testid') ?? '');
283
283
  return (
284
+ Boolean(menu.querySelector('[data-reasoning-slider="true"] [role="slider"][aria-valuemax]')) ||
284
285
  testId.includes('composer intelligence picker content') ||
285
286
  (
286
287
  text.includes('intelligence') &&
@@ -297,22 +298,21 @@ function buildThinkingTimeExpression(level) {
297
298
  if (!menu) return null;
298
299
  const target = findOptionInMenu(menu, { currentIntelligence: true });
299
300
  if (!target) {
300
- const slider = menu.querySelector(
301
- '[data-model-reasoning-effort-slider] [role="slider"][aria-valuemax]'
302
- );
301
+ const slider = menu.querySelector('[role="slider"][aria-valuemax]');
303
302
  if (TARGET_LEVEL !== 'pro' || !slider) return null;
304
303
  const max = Number(slider.getAttribute('aria-valuemax'));
305
304
  const before = Number(slider.getAttribute('aria-valuenow'));
306
305
  if (!Number.isFinite(max) || !Number.isFinite(before)) return null;
307
306
  if (before < max) {
308
- slider.focus?.();
309
- slider.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', code: 'End', bubbles: true }));
310
- slider.dispatchEvent(new KeyboardEvent('keyup', { key: 'End', code: 'End', bubbles: true }));
311
- await sleep(STEP_WAIT_MS);
312
- const updated = findCurrentIntelligenceMenu()?.querySelector(
313
- '[data-model-reasoning-effort-slider] [role="slider"][aria-valuemax]'
314
- ) ?? slider;
315
- if (Number(updated.getAttribute('aria-valuenow')) < max) return null;
307
+ const control = slider.closest?.('[data-reasoning-slider="true"]') ?? slider;
308
+ control.focus?.();
309
+ for (let step = 0; step < 5; step++) {
310
+ if (Number(menu.querySelector('[role="slider"][aria-valuemax]')?.getAttribute('aria-valuenow')) >= max) break;
311
+ control.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', code: 'ArrowRight', bubbles: true }));
312
+ control.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowRight', code: 'ArrowRight', bubbles: true }));
313
+ await sleep(STEP_WAIT_MS);
314
+ }
315
+ if (Number(menu.querySelector('[role="slider"][aria-valuemax]')?.getAttribute('aria-valuenow')) < max) return null;
316
316
  }
317
317
  return { status: before >= max ? 'already-selected' : 'switched', label: 'Pro' };
318
318
  }
@@ -63,7 +63,7 @@ export const UPLOAD_STATUS_SELECTORS = [
63
63
  '[aria-live="polite"]',
64
64
  '[aria-live="assertive"]',
65
65
  ];
66
- export const STOP_BUTTON_SELECTOR = '[data-testid="stop-button"]';
66
+ export const STOP_BUTTON_SELECTOR = '[data-testid="stop-button"], button[aria-label="Stop"]';
67
67
  export const SEND_BUTTON_SELECTORS = [
68
68
  'button[data-testid="send-button"]',
69
69
  'button[data-testid*="composer-send"]',
@@ -72,7 +72,7 @@ export const SEND_BUTTON_SELECTORS = [
72
72
  'button[aria-label*="Send"]',
73
73
  ];
74
74
  export const SEND_BUTTON_SELECTOR = SEND_BUTTON_SELECTORS[0];
75
- export const MODEL_BUTTON_SELECTOR = '[data-testid="model-switcher-dropdown-button"], button.__composer-pill[aria-haspopup="menu"], button.__composer-pill';
75
+ export const MODEL_BUTTON_SELECTOR = 'button[aria-label="Select ChatGPT model"], [data-testid="model-switcher-dropdown-button"], button.__composer-pill[aria-haspopup="menu"], button.__composer-pill';
76
76
  export const COPY_BUTTON_SELECTORS = [
77
77
  'button[data-testid="copy-turn-action-button"]',
78
78
  'button[aria-label="Copy response"]',
@@ -20,7 +20,7 @@ import { CHATGPT_URL, CONVERSATION_TURN_SELECTOR, DEFAULT_MODEL_STRATEGY } from
20
20
  import { AssistantStoppedError, BrowserAutomationError } from "./errors.js";
21
21
  import { defaultAskProBrowserProfileDir } from "./profilePaths.js";
22
22
  import { applyPageLanguageOverrides, seedChromeProfileLanguage } from "./language.js";
23
- import { alignPromptEchoPair, buildPromptEchoMatcher, withTimeout } from "./reattachHelpers.js";
23
+ import { alignPromptEchoPair, buildPromptEchoMatcher, extractConversationIdFromUrl, withTimeout, } from "./reattachHelpers.js";
24
24
  import { cleanupStaleProfileState, acquireProfileRunLock, createManagedChromeRunLease, releaseManagedChromeRunLeaseAndCountPeers, readDevToolsPort, shouldCleanupManualLoginProfileState, verifyDevToolsReachable, writeChromePid, writeDevToolsActivePort, } from "./profileState.js";
25
25
  import { runProviderSubmissionFlow } from "./providerDomFlow.js";
26
26
  import { chatgptDomProvider } from "./providers/chatgptDomProvider.js";
@@ -851,13 +851,7 @@ export async function runBrowserMode(options) {
851
851
  logger(`[retry] Model picker attempt ${attempt + 1}: ${error instanceof Error ? error.message : error}`);
852
852
  }
853
853
  },
854
- })).catch((error) => {
855
- const base = error instanceof Error ? error.message : String(error);
856
- const hint = appliedCookies === 0
857
- ? " No cookies were applied; sign in to ChatGPT in the opened browser, then resume."
858
- : "";
859
- throw new Error(`${base}${hint}`);
860
- });
854
+ }));
861
855
  await raceWithDisconnect(ensurePromptReady(Runtime, config.inputTimeoutMs, logger));
862
856
  logger(`Prompt textarea ready (after model switch, ${promptText.length.toLocaleString()} chars queued)`);
863
857
  }
@@ -2375,7 +2369,9 @@ export const __test__ = {
2375
2369
  isLoginRecoveryUrl,
2376
2370
  isEligibleManualLoginRecoveryTarget,
2377
2371
  isRecoveredTargetOwned,
2372
+ isConversationUrl,
2378
2373
  waitForLogin,
2374
+ waitForAssistantResponseWithReload,
2379
2375
  };
2380
2376
  export { syncCookies } from "./cookies.js";
2381
2377
  export { navigateToChatGPT, ensureNotBlocked, ensurePromptReady, ensureModelSelection, submitPrompt, waitForAssistantResponse, captureAssistantMarkdown, uploadAttachmentFile, waitForAttachmentCompletion, } from "./pageActions.js";
@@ -2388,6 +2384,7 @@ export function isWebSocketClosureError(error) {
2388
2384
  message.includes("target closed"));
2389
2385
  }
2390
2386
  async function waitForAssistantResponseWithReload(Runtime, Page, timeoutMs, logger, minTurnIndex, expectedConversationUrl, expectedConversationId, continueResponse) {
2387
+ const deadline = Date.now() + timeoutMs;
2391
2388
  try {
2392
2389
  return await waitForAssistantResponse(Runtime, timeoutMs, logger, minTurnIndex, expectedConversationId, continueResponse);
2393
2390
  }
@@ -2399,10 +2396,13 @@ async function waitForAssistantResponseWithReload(Runtime, Page, timeoutMs, logg
2399
2396
  if (!conversationUrl || !isConversationUrl(conversationUrl)) {
2400
2397
  throw error;
2401
2398
  }
2399
+ const remainingMs = deadline - Date.now();
2400
+ if (remainingMs <= 0)
2401
+ throw error;
2402
2402
  logger("Assistant response stalled; reloading conversation and retrying once");
2403
2403
  await Page.navigate({ url: conversationUrl });
2404
2404
  await delay(1000);
2405
- return await waitForAssistantResponse(Runtime, timeoutMs, logger, minTurnIndex, expectedConversationId, continueResponse);
2405
+ return await waitForAssistantResponse(Runtime, Math.max(1, deadline - Date.now()), logger, minTurnIndex, expectedConversationId, continueResponse);
2406
2406
  }
2407
2407
  }
2408
2408
  function shouldReloadAfterAssistantError(error) {
@@ -2567,7 +2567,7 @@ async function readConversationTurnCount(Runtime, logger) {
2567
2567
  return null;
2568
2568
  }
2569
2569
  function isConversationUrl(url) {
2570
- return /\/c\/[a-z0-9-]+/i.test(url);
2570
+ return Boolean(extractConversationIdFromUrl(url));
2571
2571
  }
2572
2572
  function describeDevtoolsFirewallHint(host, port) {
2573
2573
  if (!isWsl())
@@ -2589,10 +2589,6 @@ function isWsl() {
2589
2589
  return true;
2590
2590
  return os.release().toLowerCase().includes("microsoft");
2591
2591
  }
2592
- function extractConversationIdFromUrl(url) {
2593
- const match = url.match(/\/c\/([a-zA-Z0-9-]+)/);
2594
- return match?.[1];
2595
- }
2596
2592
  async function resolveUserDataBaseDir() {
2597
2593
  // On WSL, Chrome launched via Windows can choke on UNC paths; prefer a Windows-backed temp folder.
2598
2594
  if (isWsl()) {
@@ -89,6 +89,9 @@ async function closeManagedChromeIfUnused(chrome, userDataDir, inspectRemainingP
89
89
  await cleanupStaleProfileState(userDataDir, logger, { lockRemovalMode: "never" }).catch(() => undefined);
90
90
  }
91
91
  export async function resumeBrowserSession(runtime, config, logger, deps = {}) {
92
+ if (runtime.conversationId === "WEB" && runtime.tabUrl?.includes("/c/WEB:")) {
93
+ runtime = { ...runtime, conversationId: undefined, tabUrl: undefined };
94
+ }
92
95
  deps = { ...deps, continuationState: deps.continuationState ?? { used: false } };
93
96
  const recoverSession = deps.recoverSession ??
94
97
  (async (runtimeMeta, configMeta) => resumeBrowserSessionViaNewChrome(runtimeMeta, configMeta, logger, deps));
@@ -21,12 +21,12 @@ export function pickTarget(targets, runtime) {
21
21
  export function extractConversationIdFromUrl(url) {
22
22
  if (!url)
23
23
  return undefined;
24
- const match = url.match(/\/c\/([a-zA-Z0-9-]+)/);
24
+ const match = url.match(/\/c\/([a-zA-Z0-9-]+)(?:[/?#]|$)/);
25
25
  return match?.[1];
26
26
  }
27
27
  export function buildConversationUrl(runtime, baseUrl) {
28
28
  if (runtime.tabUrl) {
29
- if (runtime.tabUrl.includes("/c/")) {
29
+ if (extractConversationIdFromUrl(runtime.tabUrl)) {
30
30
  return runtime.tabUrl;
31
31
  }
32
32
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ask-pro",
3
- "version": "0.1.44",
3
+ "version": "0.1.48",
4
4
  "description": "Browser-backed ChatGPT Pro escalation for hard engineering questions.",
5
5
  "keywords": [
6
6
  "agents",