codex-chatgpt-control 0.5.0-alpha.1 → 0.5.1-alpha.2
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/CHANGELOG.md +22 -0
- package/contracts/v1/fixtures/backend-capabilities.json +1 -0
- package/contracts/v1/fixtures/command-descriptors.json +29 -2
- package/contracts/v1/fixtures/doctor-bridge-upload.json +2 -2
- package/contracts/v1/fixtures/doctor-scenario-preflight.json +26 -4
- package/contracts/v1/fixtures/files-attach-indeterminate.json +30 -0
- package/contracts/v1/fixtures/help-root.json +1 -1
- package/contracts/v1/fixtures/messages-stop-indeterminate.json +31 -0
- package/contracts/v1/fixtures/messages-stop-needs-confirmation.json +20 -0
- package/contracts/v1/fixtures/messages-stop-noop.json +19 -0
- package/contracts/v1/fixtures/messages-stop-success.json +21 -0
- package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +3 -3
- package/contracts/v1/fixtures/reports-create-redacted.json +3 -3
- package/contracts/v1/fixtures/reports-summarize-redacted.json +1 -1
- package/contracts/v1/fixtures/run-browser-bridge-blocker.json +12 -12
- package/contracts/v1/fixtures/stream-blocked.ndjson +2 -2
- package/contracts/v1/manifest.json +25 -0
- package/contracts/v1/parity-suite.json +19 -1
- package/dist/codex-chatgpt-control-backend.mjs +3654 -603
- package/dist/codex-chatgpt-control.bundle.mjs +3627 -546
- package/dist/src/backend/client.d.ts +2 -1
- package/dist/src/backend/client.js +1 -0
- package/dist/src/backend/protocol.d.ts +1 -1
- package/dist/src/backend/protocol.js +1 -0
- package/dist/src/backend/session.js +2 -0
- package/dist/src/browser/attach.d.ts +1 -0
- package/dist/src/browser/attach.js +69 -35
- package/dist/src/browser/chatgpt-url.d.ts +8 -0
- package/dist/src/browser/chatgpt-url.js +28 -0
- package/dist/src/browser/downloads.d.ts +1 -1
- package/dist/src/browser/downloads.js +13 -4
- package/dist/src/browser/page-state.js +211 -4
- package/dist/src/client.d.ts +2 -1
- package/dist/src/client.js +2 -1
- package/dist/src/commands/configuration.js +330 -73
- package/dist/src/commands/context.d.ts +5 -1
- package/dist/src/commands/context.js +2 -2
- package/dist/src/commands/conversation.js +13 -1
- package/dist/src/commands/doctor.js +24 -2
- package/dist/src/commands/experience.d.ts +1 -0
- package/dist/src/commands/experience.js +156 -23
- package/dist/src/commands/files.d.ts +1 -0
- package/dist/src/commands/files.js +877 -170
- package/dist/src/commands/messages.d.ts +2 -1
- package/dist/src/commands/messages.js +310 -2
- package/dist/src/commands/modes.js +197 -52
- package/dist/src/commands/registry.js +8 -0
- package/dist/src/commands/sequence.js +3 -1
- package/dist/src/commands/session.d.ts +4 -1
- package/dist/src/commands/session.js +34 -3
- package/dist/src/dom/generation-state.d.ts +6 -1
- package/dist/src/dom/generation-state.js +206 -45
- package/dist/src/dom/locale/am.d.ts +23 -1
- package/dist/src/dom/locale/am.js +23 -1
- package/dist/src/dom/locale/ar.d.ts +21 -1
- package/dist/src/dom/locale/ar.js +24 -4
- package/dist/src/dom/locale/bg.d.ts +19 -1
- package/dist/src/dom/locale/bg.js +22 -4
- package/dist/src/dom/locale/bn.d.ts +23 -1
- package/dist/src/dom/locale/bn.js +23 -1
- package/dist/src/dom/locale/bs.d.ts +17 -1
- package/dist/src/dom/locale/bs.js +21 -5
- package/dist/src/dom/locale/ca.d.ts +20 -1
- package/dist/src/dom/locale/ca.js +25 -6
- package/dist/src/dom/locale/cs.d.ts +17 -1
- package/dist/src/dom/locale/cs.js +17 -1
- package/dist/src/dom/locale/da.d.ts +17 -1
- package/dist/src/dom/locale/da.js +18 -2
- package/dist/src/dom/locale/de.d.ts +16 -1
- package/dist/src/dom/locale/de.js +18 -3
- package/dist/src/dom/locale/el.d.ts +18 -1
- package/dist/src/dom/locale/el.js +21 -4
- package/dist/src/dom/locale/en.d.ts +2 -1
- package/dist/src/dom/locale/en.js +2 -1
- package/dist/src/dom/locale/es-419.d.ts +18 -1
- package/dist/src/dom/locale/es-419.js +18 -1
- package/dist/src/dom/locale/es-ES.d.ts +18 -1
- package/dist/src/dom/locale/es-ES.js +18 -1
- package/dist/src/dom/locale/et.d.ts +16 -1
- package/dist/src/dom/locale/et.js +16 -1
- package/dist/src/dom/locale/fa.d.ts +21 -1
- package/dist/src/dom/locale/fa.js +23 -3
- package/dist/src/dom/locale/fi.d.ts +18 -1
- package/dist/src/dom/locale/fi.js +18 -1
- package/dist/src/dom/locale/fr-CA.d.ts +15 -1
- package/dist/src/dom/locale/fr-CA.js +15 -1
- package/dist/src/dom/locale/fr-FR.d.ts +18 -1
- package/dist/src/dom/locale/fr-FR.js +22 -5
- package/dist/src/dom/locale/gu.d.ts +23 -1
- package/dist/src/dom/locale/gu.js +27 -5
- package/dist/src/dom/locale/hi.d.ts +23 -1
- package/dist/src/dom/locale/hi.js +25 -3
- package/dist/src/dom/locale/hr.d.ts +16 -1
- package/dist/src/dom/locale/hr.js +19 -4
- package/dist/src/dom/locale/hu.d.ts +17 -1
- package/dist/src/dom/locale/hu.js +20 -4
- package/dist/src/dom/locale/hy.d.ts +23 -1
- package/dist/src/dom/locale/hy.js +26 -4
- package/dist/src/dom/locale/id.d.ts +17 -1
- package/dist/src/dom/locale/id.js +19 -3
- package/dist/src/dom/locale/index.js +1 -0
- package/dist/src/dom/locale/is.d.ts +21 -1
- package/dist/src/dom/locale/is.js +21 -1
- package/dist/src/dom/locale/it.d.ts +15 -1
- package/dist/src/dom/locale/it.js +20 -6
- package/dist/src/dom/locale/ja.d.ts +19 -1
- package/dist/src/dom/locale/ja.js +23 -5
- package/dist/src/dom/locale/ka.d.ts +23 -1
- package/dist/src/dom/locale/ka.js +23 -1
- package/dist/src/dom/locale/kk.d.ts +23 -1
- package/dist/src/dom/locale/kk.js +25 -3
- package/dist/src/dom/locale/kn.d.ts +22 -1
- package/dist/src/dom/locale/kn.js +26 -5
- package/dist/src/dom/locale/ko.d.ts +17 -1
- package/dist/src/dom/locale/ko.js +17 -1
- package/dist/src/dom/locale/lt.d.ts +18 -1
- package/dist/src/dom/locale/lt.js +20 -3
- package/dist/src/dom/locale/lv.d.ts +18 -1
- package/dist/src/dom/locale/lv.js +18 -1
- package/dist/src/dom/locale/mk.d.ts +22 -1
- package/dist/src/dom/locale/mk.js +26 -5
- package/dist/src/dom/locale/ml.d.ts +23 -1
- package/dist/src/dom/locale/ml.js +25 -3
- package/dist/src/dom/locale/mn.d.ts +23 -1
- package/dist/src/dom/locale/mn.js +23 -1
- package/dist/src/dom/locale/mr.d.ts +23 -1
- package/dist/src/dom/locale/mr.js +25 -3
- package/dist/src/dom/locale/ms.d.ts +20 -1
- package/dist/src/dom/locale/ms.js +20 -1
- package/dist/src/dom/locale/my.d.ts +22 -1
- package/dist/src/dom/locale/my.js +24 -3
- package/dist/src/dom/locale/nb.d.ts +17 -1
- package/dist/src/dom/locale/nb.js +17 -1
- package/dist/src/dom/locale/nl.d.ts +20 -2
- package/dist/src/dom/locale/nl.js +22 -4
- package/dist/src/dom/locale/pa.d.ts +23 -1
- package/dist/src/dom/locale/pa.js +27 -5
- package/dist/src/dom/locale/pl.d.ts +17 -1
- package/dist/src/dom/locale/pl.js +19 -3
- package/dist/src/dom/locale/pt-BR.d.ts +18 -1
- package/dist/src/dom/locale/pt-BR.js +20 -3
- package/dist/src/dom/locale/pt-PT.d.ts +18 -1
- package/dist/src/dom/locale/pt-PT.js +22 -5
- package/dist/src/dom/locale/ro.d.ts +15 -1
- package/dist/src/dom/locale/ro.js +18 -4
- package/dist/src/dom/locale/ru.d.ts +23 -1
- package/dist/src/dom/locale/ru.js +24 -2
- package/dist/src/dom/locale/sk.d.ts +17 -1
- package/dist/src/dom/locale/sk.js +19 -3
- package/dist/src/dom/locale/sl.d.ts +21 -1
- package/dist/src/dom/locale/sl.js +24 -4
- package/dist/src/dom/locale/so.d.ts +22 -1
- package/dist/src/dom/locale/so.js +24 -3
- package/dist/src/dom/locale/sq.d.ts +21 -1
- package/dist/src/dom/locale/sq.js +25 -5
- package/dist/src/dom/locale/sr.d.ts +24 -1
- package/dist/src/dom/locale/sr.js +25 -2
- package/dist/src/dom/locale/sv.d.ts +16 -1
- package/dist/src/dom/locale/sv.js +16 -1
- package/dist/src/dom/locale/sw.d.ts +18 -1
- package/dist/src/dom/locale/sw.js +18 -1
- package/dist/src/dom/locale/ta.d.ts +23 -1
- package/dist/src/dom/locale/ta.js +27 -5
- package/dist/src/dom/locale/te.d.ts +23 -1
- package/dist/src/dom/locale/te.js +27 -5
- package/dist/src/dom/locale/th.d.ts +11 -1
- package/dist/src/dom/locale/th.js +11 -1
- package/dist/src/dom/locale/tl.d.ts +8 -1
- package/dist/src/dom/locale/tl.js +8 -1
- package/dist/src/dom/locale/tr.d.ts +21 -1
- package/dist/src/dom/locale/tr.js +23 -3
- package/dist/src/dom/locale/types.d.ts +1 -1
- package/dist/src/dom/locale/uk.d.ts +23 -1
- package/dist/src/dom/locale/uk.js +25 -3
- package/dist/src/dom/locale/ur.d.ts +22 -1
- package/dist/src/dom/locale/ur.js +25 -4
- package/dist/src/dom/locale/vi.d.ts +22 -1
- package/dist/src/dom/locale/vi.js +25 -4
- package/dist/src/dom/locale/zh-HK.d.ts +21 -1
- package/dist/src/dom/locale/zh-HK.js +23 -3
- package/dist/src/dom/locale/zh-Hans.d.ts +23 -1
- package/dist/src/dom/locale/zh-Hans.js +27 -5
- package/dist/src/dom/locale/zh-TW.d.ts +23 -1
- package/dist/src/dom/locale/zh-TW.js +25 -3
- package/dist/src/dom/menus.js +26 -2
- package/dist/src/dom/selectors.d.ts +1 -0
- package/dist/src/dom/selectors.js +9 -1
- package/dist/src/dom/wait-snapshot.d.ts +1 -0
- package/dist/src/dom/wait-snapshot.js +45 -22
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +2 -2
- package/dist/src/platform/local-paths.d.ts +1 -0
- package/dist/src/platform/local-paths.js +7 -3
- package/dist/src/safety/risk.d.ts +1 -0
- package/dist/src/safety/risk.js +1 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +10 -1
- package/dist/src/scripts/apply-intelligence-locale-captures.js +219 -7
- package/dist/src/scripts/capture-intelligence-locales.d.ts +119 -1
- package/dist/src/scripts/capture-intelligence-locales.js +653 -232
- package/dist/src/scripts/capture-surface-profile.d.ts +3 -1
- package/dist/src/scripts/capture-surface-profile.js +75 -25
- package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
- package/dist/src/scripts/live-smoke/scenarios.js +321 -13
- package/dist/src/scripts/live-smoke.js +1 -0
- package/dist/src/scripts/locale-capture/surface-graph.d.ts +31 -0
- package/dist/src/scripts/locale-capture/surface-graph.js +50 -0
- package/dist/src/scripts/release-canary-module.d.ts +20 -0
- package/dist/src/scripts/release-canary-module.js +86 -0
- package/dist/src/types.d.ts +30 -6
- package/package.json +8 -2
- package/references/2026-07-16-chat-work-surfaces.md +13 -0
- package/references/2026-08-08-model-selection-locale-sweep.md +32 -0
- package/references/backend-protocol.md +22 -7
- package/references/language-coverage.md +24 -13
- package/references/localization.md +36 -14
- package/references/python-parity.md +9 -3
- package/references/troubleshooting.md +26 -4
|
@@ -2,11 +2,14 @@ import { enumerateVisibleMenuItems } from "../dom/menus.js";
|
|
|
2
2
|
import { localeLabels } from "../dom/locale-labels.js";
|
|
3
3
|
import { normalizeForLabelMatch, visibleLabelMatches } from "../dom/label-match.js";
|
|
4
4
|
import { resultError, resultOk } from "../errors.js";
|
|
5
|
+
import { tabIdFromPage } from "../browser/attach.js";
|
|
5
6
|
import { contextFromPage } from "./context.js";
|
|
6
7
|
import { detectExperience, openExperience } from "./experience.js";
|
|
7
8
|
import { setMode } from "./modes.js";
|
|
8
9
|
import { ensurePage } from "./session.js";
|
|
9
10
|
const WORK_AXES = ["model", "effort", "speed"];
|
|
11
|
+
const CONFIGURATION_CONTROL_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
12
|
+
const CONFIGURATION_CONTROL_POLL_MS = 250;
|
|
10
13
|
const CONFIGURATION_AXIS_ORDER = [
|
|
11
14
|
"model",
|
|
12
15
|
"intelligence",
|
|
@@ -44,18 +47,27 @@ export async function inspectConfiguration(env, args = {}) {
|
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
const experience = detected.data.experience;
|
|
47
|
-
const
|
|
50
|
+
const initialPanel = await readConfigurationPanel(page);
|
|
51
|
+
const rootOpened = experience !== "unknown" && await waitForConfigurationRoot(page, experience, args.timeoutMs);
|
|
48
52
|
if (rootOpened) {
|
|
49
53
|
await page.waitForTimeout?.(150);
|
|
50
54
|
}
|
|
55
|
+
const workAdvancedOpened = experience !== "work"
|
|
56
|
+
|| (rootOpened && await ensureWorkAdvancedPanel(page));
|
|
57
|
+
if (experience === "work" && workAdvancedOpened) {
|
|
58
|
+
await page.waitForTimeout?.(150);
|
|
59
|
+
}
|
|
51
60
|
const panel = await readConfigurationPanel(page);
|
|
61
|
+
if (panel.openerLabel === undefined && initialPanel.openerLabel !== undefined) {
|
|
62
|
+
panel.openerLabel = initialPanel.openerLabel;
|
|
63
|
+
}
|
|
52
64
|
const rootItems = rootOpened ? await enumerateVisibleMenuItems(page) : [];
|
|
53
65
|
const data = configurationInspectionFromSurface(experience, detected.data.selectorProfile, detected.data.evidence, panel, rootItems);
|
|
54
66
|
if (args.includeOptions !== false && experience === "work" && panel.axisRows.length > 0) {
|
|
55
67
|
for (const axis of WORK_AXES) {
|
|
56
68
|
if (!data.availableAxes.includes(axis))
|
|
57
69
|
continue;
|
|
58
|
-
const options = await inspectWorkAxisOptions(
|
|
70
|
+
const options = await inspectWorkAxisOptions(env, axis);
|
|
59
71
|
if (options.length > 0) {
|
|
60
72
|
data.options[axis] = options;
|
|
61
73
|
}
|
|
@@ -66,6 +78,9 @@ export async function inspectConfiguration(env, args = {}) {
|
|
|
66
78
|
if (!rootOpened) {
|
|
67
79
|
warnings.push("No scoped configuration opener was available; inspection is limited to controls already visible in the composer.");
|
|
68
80
|
}
|
|
81
|
+
if (experience === "work" && rootOpened && !workAdvancedOpened) {
|
|
82
|
+
warnings.push("The Work configuration menu opened, but its Advanced model, effort, and speed controls could not be made visible.");
|
|
83
|
+
}
|
|
69
84
|
if (!data.verified) {
|
|
70
85
|
warnings.push("The visible configuration could not be verified from a recognized Chat or Work selector profile.");
|
|
71
86
|
}
|
|
@@ -78,6 +93,18 @@ export async function inspectConfiguration(env, args = {}) {
|
|
|
78
93
|
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
79
94
|
}
|
|
80
95
|
}
|
|
96
|
+
async function waitForConfigurationRoot(page, experience, timeoutMs) {
|
|
97
|
+
const discoveryMs = Math.min(timeoutMs ?? CONFIGURATION_CONTROL_DISCOVERY_TIMEOUT_MS, CONFIGURATION_CONTROL_DISCOVERY_TIMEOUT_MS);
|
|
98
|
+
const attempts = Math.max(1, Math.ceil(Math.max(0, discoveryMs) / CONFIGURATION_CONTROL_POLL_MS));
|
|
99
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
100
|
+
if (await openConfigurationRoot(page, experience))
|
|
101
|
+
return true;
|
|
102
|
+
if (attempt + 1 < attempts) {
|
|
103
|
+
await page.waitForTimeout?.(CONFIGURATION_CONTROL_POLL_MS);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
81
108
|
export async function applyConfiguration(env, args) {
|
|
82
109
|
const boot = await ensurePage(env);
|
|
83
110
|
if (!boot.ok) {
|
|
@@ -131,7 +158,7 @@ export async function applyConfiguration(env, args) {
|
|
|
131
158
|
continue;
|
|
132
159
|
}
|
|
133
160
|
const selection = before.experience === "work"
|
|
134
|
-
? await selectWorkAxis(
|
|
161
|
+
? await selectWorkAxis(env, axis, requested)
|
|
135
162
|
: await selectChatAxis(env, axis, requested, args.timeoutMs);
|
|
136
163
|
if (selection === undefined) {
|
|
137
164
|
return configurationFailure(page, before, desired, selected, `Configuration option "${requested}" for ${axis} was not found or was ambiguous on the ${before.experience} surface.`, "configuration_option_not_found", before.options[axis]?.map(option => option.label));
|
|
@@ -227,36 +254,168 @@ export function configurationInspectionFromSurface(experience, detectedProfile,
|
|
|
227
254
|
evidence
|
|
228
255
|
};
|
|
229
256
|
}
|
|
230
|
-
async function inspectWorkAxisOptions(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return [];
|
|
234
|
-
}
|
|
235
|
-
await page.waitForTimeout?.(120);
|
|
236
|
-
const items = await enumerateVisibleMenuItems(page);
|
|
237
|
-
const options = items
|
|
238
|
-
.filter(item => !isConfigurationAxisRow(item.label))
|
|
257
|
+
async function inspectWorkAxisOptions(env, axis) {
|
|
258
|
+
const page = env.page;
|
|
259
|
+
const options = (await openWorkAxisOptions(env, axis))
|
|
239
260
|
.map(menuItemToOption);
|
|
240
|
-
await page
|
|
241
|
-
await page.waitForTimeout?.(80);
|
|
261
|
+
await closeConfigurationSubmenu(page);
|
|
242
262
|
return dedupeOptions(options);
|
|
243
263
|
}
|
|
244
|
-
async function selectWorkAxis(
|
|
264
|
+
async function selectWorkAxis(env, axis, requested) {
|
|
265
|
+
const page = env.page;
|
|
245
266
|
if (!WORK_AXES.includes(axis)) {
|
|
246
267
|
return undefined;
|
|
247
268
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
269
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
270
|
+
const candidates = await openWorkAxisOptions(env, axis);
|
|
271
|
+
const match = findConfigurationOption(candidates, requested);
|
|
272
|
+
if (match !== undefined && await clickVisibleMenuItem(page, match)) {
|
|
273
|
+
await page.waitForTimeout?.(150);
|
|
274
|
+
return match.label;
|
|
275
|
+
}
|
|
276
|
+
if (attempt === 0) {
|
|
277
|
+
// The submenu can disappear between enumeration and the visible click
|
|
278
|
+
// when ChatGPT's pointer-grace timer fires. Reopen once and click from a
|
|
279
|
+
// fresh DOM instance instead of treating that transient race as drift.
|
|
280
|
+
await closeConfigurationMenus(page);
|
|
281
|
+
await page.waitForTimeout?.(200);
|
|
282
|
+
}
|
|
251
283
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
async function openWorkAxisOptions(env, axis, allowRootRetry = true) {
|
|
287
|
+
const page = env.page;
|
|
288
|
+
if (!await ensureWorkAdvancedPanel(page))
|
|
289
|
+
return [];
|
|
290
|
+
const row = await findWorkAxisRow(page, axis);
|
|
291
|
+
if (row === undefined)
|
|
292
|
+
return [];
|
|
293
|
+
const visibleOptions = async () => filterWorkAxisOptions(await enumerateVisibleMenuItems(page), axis);
|
|
294
|
+
const alreadyOpen = await visibleOptions();
|
|
295
|
+
if (alreadyOpen.length > 0)
|
|
296
|
+
return alreadyOpen;
|
|
297
|
+
const point = await locatorCenter(row);
|
|
298
|
+
if (point !== undefined && await movePointerWithCdp(env, point)) {
|
|
299
|
+
await page.waitForTimeout?.(180);
|
|
300
|
+
const hoveredOptions = await visibleOptions();
|
|
301
|
+
if (hoveredOptions.length > 0)
|
|
302
|
+
return hoveredOptions;
|
|
303
|
+
// Radix may apply the previous submenu's pointer-grace close after the new
|
|
304
|
+
// axis briefly opens. Once that timer has elapsed, a tiny in-row nudge
|
|
305
|
+
// creates a fresh hover transition without ever leaving the root menu.
|
|
306
|
+
await movePointerWithCdp(env, { x: point.x - 2, y: point.y });
|
|
307
|
+
await movePointerWithCdp(env, point);
|
|
308
|
+
await page.waitForTimeout?.(180);
|
|
309
|
+
const retriedOptions = await visibleOptions();
|
|
310
|
+
if (retriedOptions.length > 0)
|
|
311
|
+
return retriedOptions;
|
|
312
|
+
}
|
|
313
|
+
if (point !== undefined && page.mouse?.move !== undefined) {
|
|
314
|
+
try {
|
|
315
|
+
await page.mouse.move(point.x, point.y);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Fall through to the Computer Use and click fallbacks.
|
|
319
|
+
}
|
|
320
|
+
await page.waitForTimeout?.(180);
|
|
321
|
+
const mouseOptions = await visibleOptions();
|
|
322
|
+
if (mouseOptions.length > 0)
|
|
323
|
+
return mouseOptions;
|
|
324
|
+
}
|
|
325
|
+
if (point !== undefined && typeof page.cua?.move === "function") {
|
|
326
|
+
try {
|
|
327
|
+
await page.cua.move(point);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// Fall through to the click fallback.
|
|
331
|
+
}
|
|
332
|
+
await page.waitForTimeout?.(180);
|
|
333
|
+
const movedOptions = await visibleOptions();
|
|
334
|
+
if (movedOptions.length > 0)
|
|
335
|
+
return movedOptions;
|
|
336
|
+
}
|
|
337
|
+
if (row.click !== undefined) {
|
|
338
|
+
await row.click().catch(() => undefined);
|
|
339
|
+
await page.waitForTimeout?.(180);
|
|
340
|
+
}
|
|
341
|
+
const clickedOptions = await visibleOptions();
|
|
342
|
+
if (clickedOptions.length > 0 || !allowRootRetry)
|
|
343
|
+
return clickedOptions;
|
|
344
|
+
// A submenu pointer-grace dismissal can also close the root menu. Reopen the
|
|
345
|
+
// complete Advanced panel once and resolve a fresh row locator; stale row
|
|
346
|
+
// handles and same-coordinate pointer moves cannot recover a detached menu.
|
|
347
|
+
await closeConfigurationMenus(page);
|
|
348
|
+
await page.waitForTimeout?.(200);
|
|
349
|
+
return openWorkAxisOptions(env, axis, false);
|
|
350
|
+
}
|
|
351
|
+
async function locatorCenter(locator) {
|
|
352
|
+
if (locator.evaluate === undefined)
|
|
256
353
|
return undefined;
|
|
354
|
+
return locator.evaluate(element => {
|
|
355
|
+
const rect = element.getBoundingClientRect();
|
|
356
|
+
return {
|
|
357
|
+
x: Math.round(rect.left + rect.width / 2),
|
|
358
|
+
y: Math.round(rect.top + rect.height / 2)
|
|
359
|
+
};
|
|
360
|
+
}).catch(() => undefined);
|
|
361
|
+
}
|
|
362
|
+
async function movePointerWithCdp(env, point) {
|
|
363
|
+
const page = env.page;
|
|
364
|
+
const tabId = page === undefined ? undefined : tabIdFromPage(page);
|
|
365
|
+
if (tabId === undefined || env.browser?.tabs?.get === undefined)
|
|
366
|
+
return false;
|
|
367
|
+
try {
|
|
368
|
+
const tab = await env.browser.tabs.get(tabId);
|
|
369
|
+
const capability = await tab.capabilities?.get?.("cdp");
|
|
370
|
+
if (capability?.send === undefined)
|
|
371
|
+
return false;
|
|
372
|
+
// Move directly between axis rows. Moving outside the Radix root menu first
|
|
373
|
+
// schedules its dismissal; the submenu can briefly appear and then vanish
|
|
374
|
+
// before the bounded option inspection runs.
|
|
375
|
+
await capability.send("Input.dispatchMouseEvent", {
|
|
376
|
+
type: "mouseMoved",
|
|
377
|
+
x: Math.round(point.x),
|
|
378
|
+
y: Math.round(point.y),
|
|
379
|
+
button: "none",
|
|
380
|
+
buttons: 0,
|
|
381
|
+
pointerType: "mouse"
|
|
382
|
+
});
|
|
383
|
+
return true;
|
|
257
384
|
}
|
|
258
|
-
|
|
259
|
-
|
|
385
|
+
catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function filterWorkAxisOptions(items, axis) {
|
|
390
|
+
return items.filter(item => {
|
|
391
|
+
if (isConfigurationAxisRow(item.label))
|
|
392
|
+
return false;
|
|
393
|
+
// Keep every choice axis-specific. This excludes parent actions such as
|
|
394
|
+
// "Reset to default" and prevents a still-open model submenu from being
|
|
395
|
+
// mistaken for effort or speed choices if Escape is unavailable.
|
|
396
|
+
return workAxisOptionLabelMatches(axis, item.label);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function workAxisOptionLabelMatches(axis, label) {
|
|
400
|
+
if (axis === "model") {
|
|
401
|
+
return /\b(?:gpt[\s-]?\d|sol|luna|terra)\b/i.test(label);
|
|
402
|
+
}
|
|
403
|
+
const candidates = axis === "effort"
|
|
404
|
+
? [
|
|
405
|
+
...localeLabels.configurationOptions.light,
|
|
406
|
+
...localeLabels.configurationOptions.medium,
|
|
407
|
+
...localeLabels.configurationOptions.high,
|
|
408
|
+
...localeLabels.configurationOptions.extraHigh,
|
|
409
|
+
...localeLabels.configurationOptions.max,
|
|
410
|
+
...localeLabels.configurationOptions.ultra,
|
|
411
|
+
]
|
|
412
|
+
: axis === "speed"
|
|
413
|
+
? [
|
|
414
|
+
...localeLabels.configurationOptions.standard,
|
|
415
|
+
...localeLabels.configurationOptions.fast,
|
|
416
|
+
]
|
|
417
|
+
: [];
|
|
418
|
+
return candidates.some(candidate => visibleLabelMatches(label, candidate));
|
|
260
419
|
}
|
|
261
420
|
async function selectChatAxis(env, axis, requested, timeoutMs) {
|
|
262
421
|
const legacyArgs = axis === "modelVersion"
|
|
@@ -286,20 +445,46 @@ async function openConfigurationRoot(page, experience) {
|
|
|
286
445
|
if (configurationMenuLooksRecognized(existingItems, experience, existing.openerLabel)) {
|
|
287
446
|
return true;
|
|
288
447
|
}
|
|
448
|
+
if (existing.openerLabel !== undefined
|
|
449
|
+
&& await clickIfUnique(page.getByRole?.("button", { name: existing.openerLabel, exact: true }))) {
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
289
452
|
if (typeof page.evaluate === "function") {
|
|
290
453
|
const clicked = await page.evaluate((surface) => {
|
|
291
454
|
const normalize = (value) => value.replace(/\s+/g, " ").trim();
|
|
292
455
|
const visible = (element) => {
|
|
293
456
|
const html = element;
|
|
294
457
|
const rect = html.getBoundingClientRect?.();
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
458
|
+
if (rect !== undefined && (rect.width <= 0 || rect.height <= 0))
|
|
459
|
+
return false;
|
|
460
|
+
let current = element;
|
|
461
|
+
while (current !== null) {
|
|
462
|
+
if (current.hasAttribute?.("inert") || current.getAttribute?.("aria-hidden") === "true") {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
const style = typeof window !== "undefined"
|
|
466
|
+
? window.getComputedStyle?.(current)
|
|
467
|
+
: undefined;
|
|
468
|
+
if (style?.display === "none" || style?.visibility === "hidden" || style?.opacity === "0") {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
current = current.parentElement ?? null;
|
|
472
|
+
}
|
|
473
|
+
return true;
|
|
300
474
|
};
|
|
301
|
-
const
|
|
302
|
-
const
|
|
475
|
+
const formRoots = Array.from(document.querySelectorAll("main form"));
|
|
476
|
+
const testIdRoots = Array.from(document.querySelectorAll("main [data-testid*='composer' i]"));
|
|
477
|
+
const classRoots = Array.from(document.querySelectorAll("main [class*='composer' i]"));
|
|
478
|
+
const composerRoots = formRoots.length > 0
|
|
479
|
+
? formRoots
|
|
480
|
+
: testIdRoots.length > 0
|
|
481
|
+
? testIdRoots
|
|
482
|
+
: classRoots;
|
|
483
|
+
const main = document.querySelector("main");
|
|
484
|
+
const roots = Array.from(new Set(composerRoots.length > 0
|
|
485
|
+
? composerRoots
|
|
486
|
+
: (main === null ? [] : [main])));
|
|
487
|
+
const controls = Array.from(new Set(roots.flatMap(root => Array.from(root.querySelectorAll("button, [role='button']")))))
|
|
303
488
|
.filter(visible);
|
|
304
489
|
const matches = controls.filter(control => {
|
|
305
490
|
const html = control;
|
|
@@ -347,6 +532,9 @@ function configurationMenuLooksRecognized(items, experience, openerLabel) {
|
|
|
347
532
|
if (items.some(item => /(?:model|mode|effort|speed)-(?:switcher|selector)|model-switcher/i.test(item.testId ?? ""))) {
|
|
348
533
|
return true;
|
|
349
534
|
}
|
|
535
|
+
if (experience === "work" && items.some(item => localeLabels.configurationAxes.advanced.some(label => visibleLabelMatches(item.label, label)))) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
350
538
|
if (openerLabel === undefined || items.length === 0) {
|
|
351
539
|
return false;
|
|
352
540
|
}
|
|
@@ -374,13 +562,22 @@ function configurationMenuLooksRecognized(items, experience, openerLabel) {
|
|
|
374
562
|
.map(item => normalizeConfigurationId(item.label)));
|
|
375
563
|
return matched.size >= 2;
|
|
376
564
|
}
|
|
377
|
-
async function
|
|
565
|
+
async function ensureWorkAdvancedPanel(page) {
|
|
378
566
|
const panel = await readConfigurationPanel(page);
|
|
379
567
|
if (panel.axisRows.length > 0)
|
|
380
568
|
return true;
|
|
381
|
-
await page
|
|
382
|
-
|
|
383
|
-
|
|
569
|
+
if (!await openConfigurationRoot(page, "work"))
|
|
570
|
+
return false;
|
|
571
|
+
const reopenedPanel = await readConfigurationPanel(page);
|
|
572
|
+
if (reopenedPanel.axisRows.length > 0)
|
|
573
|
+
return true;
|
|
574
|
+
const items = await enumerateVisibleMenuItems(page);
|
|
575
|
+
const advanced = items.filter(item => localeLabels.configurationAxes.advanced.some(label => visibleLabelMatches(item.label, label)));
|
|
576
|
+
if (advanced.length !== 1 || !await clickVisibleMenuItem(page, advanced[0])) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
await page.waitForTimeout?.(200);
|
|
580
|
+
return (await readConfigurationPanel(page)).axisRows.length > 0;
|
|
384
581
|
}
|
|
385
582
|
async function readConfigurationPanel(page) {
|
|
386
583
|
if (typeof page.evaluate !== "function") {
|
|
@@ -395,24 +592,31 @@ async function readConfigurationPanel(page) {
|
|
|
395
592
|
const visible = (element) => {
|
|
396
593
|
const html = element;
|
|
397
594
|
const rect = html.getBoundingClientRect?.();
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
595
|
+
if (rect !== undefined && (rect.width <= 0 || rect.height <= 0))
|
|
596
|
+
return false;
|
|
597
|
+
let current = element;
|
|
598
|
+
while (current !== null) {
|
|
599
|
+
if (current.hasAttribute?.("inert") || current.getAttribute?.("aria-hidden") === "true") {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
const style = typeof window !== "undefined"
|
|
603
|
+
? window.getComputedStyle?.(current)
|
|
604
|
+
: undefined;
|
|
605
|
+
if (style?.display === "none" || style?.visibility === "hidden" || style?.opacity === "0") {
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
current = current.parentElement ?? null;
|
|
609
|
+
}
|
|
610
|
+
return true;
|
|
403
611
|
};
|
|
404
612
|
const overlays = Array.from(document.querySelectorAll("[role='menu'], [role='listbox'], [data-radix-popper-content-wrapper], [data-radix-menu-content]")).filter(visible);
|
|
405
613
|
const roots = overlays.length > 0 ? overlays : Array.from(document.querySelectorAll("main")).filter(visible);
|
|
406
614
|
const rows = roots.flatMap(root => Array.from(root.querySelectorAll("button, [role='button'], [role='menuitem'], [role='menuitemradio'], [role='option']"))).filter(visible);
|
|
407
615
|
const axisRows = [];
|
|
408
|
-
let advancedVisible = false;
|
|
409
616
|
for (const row of rows) {
|
|
410
617
|
const html = row;
|
|
411
618
|
const label = normalize(row.getAttribute("aria-label") ?? html.innerText ?? row.textContent ?? "");
|
|
412
619
|
const normalized = label.toLocaleLowerCase();
|
|
413
|
-
if (normalizedAxes.advanced?.some(candidate => normalized === candidate || normalized.startsWith(`${candidate} `))) {
|
|
414
|
-
advancedVisible = true;
|
|
415
|
-
}
|
|
416
620
|
for (const axis of ["model", "intelligence", "effort", "speed"]) {
|
|
417
621
|
const candidates = normalizedAxes[axis] ?? [];
|
|
418
622
|
const prefix = candidates.find(candidate => normalized === candidate || normalized.startsWith(`${candidate} `));
|
|
@@ -426,8 +630,19 @@ async function readConfigurationPanel(page) {
|
|
|
426
630
|
break;
|
|
427
631
|
}
|
|
428
632
|
}
|
|
429
|
-
const
|
|
430
|
-
const
|
|
633
|
+
const formRoots = Array.from(document.querySelectorAll("main form"));
|
|
634
|
+
const testIdRoots = Array.from(document.querySelectorAll("main [data-testid*='composer' i]"));
|
|
635
|
+
const classRoots = Array.from(document.querySelectorAll("main [class*='composer' i]"));
|
|
636
|
+
const composerRoots = formRoots.length > 0
|
|
637
|
+
? formRoots
|
|
638
|
+
: testIdRoots.length > 0
|
|
639
|
+
? testIdRoots
|
|
640
|
+
: classRoots;
|
|
641
|
+
const main = document.querySelector("main");
|
|
642
|
+
const openerRoots = Array.from(new Set(composerRoots.length > 0
|
|
643
|
+
? composerRoots
|
|
644
|
+
: (main === null ? [] : [main])));
|
|
645
|
+
const openerCandidates = Array.from(new Set(openerRoots.flatMap(root => Array.from(root.querySelectorAll("button, [role='button']")))))
|
|
431
646
|
.filter(visible)
|
|
432
647
|
.map(control => {
|
|
433
648
|
const html = control;
|
|
@@ -441,7 +656,7 @@ async function readConfigurationPanel(page) {
|
|
|
441
656
|
|| /\b(?:gpt|sol|luna|terra|instant|medium|high|extra high|pro|thinking|extended|light|standard|fast)\b/i.test(item.label));
|
|
442
657
|
const result = {
|
|
443
658
|
axisRows,
|
|
444
|
-
advancedVisible
|
|
659
|
+
advancedVisible: axisRows.length > 0
|
|
445
660
|
};
|
|
446
661
|
if (openerCandidates.length === 1 && openerCandidates[0]?.label.length) {
|
|
447
662
|
result.openerLabel = openerCandidates[0].label;
|
|
@@ -449,44 +664,47 @@ async function readConfigurationPanel(page) {
|
|
|
449
664
|
return result;
|
|
450
665
|
}, localeLabels.configurationAxes).catch(() => ({ axisRows: [], advancedVisible: false }));
|
|
451
666
|
}
|
|
452
|
-
async function
|
|
667
|
+
async function findWorkAxisRow(page, axis) {
|
|
453
668
|
const labels = axis === "modelVersion" ? [] : localeLabels.configurationAxes[axis] ?? [];
|
|
454
669
|
for (const label of labels) {
|
|
455
670
|
const pattern = new RegExp(`^${escapeRegExp(label)}(?:\\s|$)`, "i");
|
|
456
671
|
for (const role of ["button", "menuitem"]) {
|
|
457
|
-
|
|
458
|
-
|
|
672
|
+
const locator = page.getByRole?.(role, { name: pattern });
|
|
673
|
+
if (locator?.count !== undefined && await locator.count().catch(() => 0) === 1) {
|
|
674
|
+
return locator;
|
|
459
675
|
}
|
|
460
676
|
}
|
|
461
677
|
}
|
|
462
|
-
|
|
463
|
-
return false;
|
|
464
|
-
return page.evaluate((wantedLabels) => {
|
|
465
|
-
const normalize = (value) => value.replace(/\s+/g, " ").trim().toLocaleLowerCase();
|
|
466
|
-
const wanted = wantedLabels.map(normalize);
|
|
467
|
-
const overlays = Array.from(document.querySelectorAll("[role='menu'], [data-radix-popper-content-wrapper], [data-radix-menu-content]"));
|
|
468
|
-
const nodes = overlays.flatMap(root => Array.from(root.querySelectorAll("button, [role='button'], [role='menuitem']")));
|
|
469
|
-
const matches = nodes.filter(node => {
|
|
470
|
-
const html = node;
|
|
471
|
-
const label = normalize(node.getAttribute("aria-label") ?? html.innerText ?? node.textContent ?? "");
|
|
472
|
-
return wanted.some(prefix => label === prefix || label.startsWith(`${prefix} `));
|
|
473
|
-
});
|
|
474
|
-
if (matches.length !== 1)
|
|
475
|
-
return false;
|
|
476
|
-
matches[0].click();
|
|
477
|
-
return true;
|
|
478
|
-
}, labels).catch(() => false);
|
|
678
|
+
return undefined;
|
|
479
679
|
}
|
|
480
680
|
async function clickVisibleMenuItem(page, item) {
|
|
481
681
|
if (item.testId !== undefined && await clickIfUnique(page.locator?.(`[data-testid="${escapeAttributeValue(item.testId)}"]`))) {
|
|
482
682
|
return true;
|
|
483
683
|
}
|
|
484
|
-
|
|
684
|
+
const roles = [...new Set([
|
|
685
|
+
item.role,
|
|
686
|
+
"menuitemradio",
|
|
687
|
+
"menuitem",
|
|
688
|
+
"option"
|
|
689
|
+
].filter((role) => role !== undefined))];
|
|
690
|
+
// Current Chat can expose visible text such as "Advanced" while assigning
|
|
691
|
+
// the actionable menu item a different accessible name (for example,
|
|
692
|
+
// "Show compact options"). Prefer that enumerated accessible name so the
|
|
693
|
+
// bridge clicks the menu item itself rather than a nested text span.
|
|
694
|
+
if (item.ariaLabel !== undefined) {
|
|
695
|
+
for (const role of roles) {
|
|
696
|
+
if (await clickIfUnique(page.getByRole?.(role, { name: item.ariaLabel, exact: true }))) {
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
for (const role of roles) {
|
|
485
702
|
if (await clickIfUnique(page.getByRole?.(role, { name: item.label, exact: true }))) {
|
|
486
703
|
return true;
|
|
487
704
|
}
|
|
488
705
|
}
|
|
489
|
-
|
|
706
|
+
const exactText = new RegExp(`^\\s*${escapeRegExp(item.label)}\\s*$`, "i");
|
|
707
|
+
return clickIfUnique(page.locator?.("button, [role='button'], [role='menuitem'], [role='menuitemradio'], [role='option']")?.filter?.({ hasText: exactText }));
|
|
490
708
|
}
|
|
491
709
|
function findConfigurationOption(items, requested) {
|
|
492
710
|
const normalizedRequested = normalizeConfigurationId(requested);
|
|
@@ -608,16 +826,55 @@ function normalizeConfigurationId(value) {
|
|
|
608
826
|
.trim();
|
|
609
827
|
}
|
|
610
828
|
async function closeConfigurationMenus(page) {
|
|
611
|
-
await page
|
|
829
|
+
if (!await pressConfigurationEscape(page))
|
|
830
|
+
return;
|
|
612
831
|
await page.waitForTimeout?.(50);
|
|
613
|
-
await page
|
|
832
|
+
await pressConfigurationEscape(page);
|
|
833
|
+
}
|
|
834
|
+
async function closeConfigurationSubmenu(page) {
|
|
835
|
+
if (!await pressConfigurationEscape(page))
|
|
836
|
+
return;
|
|
837
|
+
// Radix retains a short pointer-grace timer after a submenu closes. If the
|
|
838
|
+
// next axis is hovered too quickly, that stale timer can dismiss the newly
|
|
839
|
+
// opened submenu. Let the prior close settle before moving to another row.
|
|
840
|
+
await page.waitForTimeout?.(200);
|
|
841
|
+
}
|
|
842
|
+
async function pressConfigurationEscape(page) {
|
|
843
|
+
if (page.keyboard?.press !== undefined) {
|
|
844
|
+
await page.keyboard.press("Escape");
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
if (page.cua?.keypress !== undefined) {
|
|
848
|
+
try {
|
|
849
|
+
await page.cua.keypress({ keys: ["ESC"] });
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
catch {
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return false;
|
|
614
857
|
}
|
|
615
858
|
async function clickIfUnique(locator) {
|
|
616
859
|
if (locator?.count === undefined || locator.click === undefined)
|
|
617
860
|
return false;
|
|
618
|
-
|
|
861
|
+
const count = await locator.count().catch(() => 0);
|
|
862
|
+
if (count === 1) {
|
|
863
|
+
await locator.click();
|
|
864
|
+
return true;
|
|
865
|
+
}
|
|
866
|
+
if (count <= 1 || locator.nth === undefined)
|
|
867
|
+
return false;
|
|
868
|
+
const visible = [];
|
|
869
|
+
for (let index = 0; index < count; index += 1) {
|
|
870
|
+
const candidate = locator.nth(index);
|
|
871
|
+
if (candidate.isVisible !== undefined && await candidate.isVisible().catch(() => false)) {
|
|
872
|
+
visible.push(candidate);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (visible.length !== 1 || visible[0]?.click === undefined)
|
|
619
876
|
return false;
|
|
620
|
-
await
|
|
877
|
+
await visible[0].click();
|
|
621
878
|
return true;
|
|
622
879
|
}
|
|
623
880
|
async function configurationFailure(page, before, desired, selected, message, code, candidates = []) {
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { CommandContext, PageLike } from "../types.js";
|
|
2
|
-
export
|
|
2
|
+
export type ContextReadOptions = {
|
|
3
|
+
/** Avoid all optional browser probes on mutation/deadline result paths. */
|
|
4
|
+
minimal?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function contextFromPage(page: PageLike | undefined, partial?: Partial<CommandContext>, options?: ContextReadOptions): Promise<CommandContext>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { countPageMessages } from "../dom/messages.js";
|
|
2
2
|
import { parseConversationId } from "../browser/page-state.js";
|
|
3
3
|
import { withTimeout } from "./timeouts.js";
|
|
4
|
-
export async function contextFromPage(page, partial = {}) {
|
|
5
|
-
if (page === undefined) {
|
|
4
|
+
export async function contextFromPage(page, partial = {}, options = {}) {
|
|
5
|
+
if (page === undefined || options.minimal === true) {
|
|
6
6
|
return { timestamp: new Date().toISOString(), ...partial };
|
|
7
7
|
}
|
|
8
8
|
const url = typeof page.url === "function" ? await Promise.resolve(page.url()).catch(() => partial.url) : partial.url;
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import { parseConversationId } from "../browser/page-state.js";
|
|
2
|
+
import { isChatGPTUrl, requireChatGPTUrl } from "../browser/chatgpt-url.js";
|
|
2
3
|
import { countPageMessages, readLatestMessageText } from "../dom/messages.js";
|
|
3
4
|
const CHATGPT_HOME = "https://chatgpt.com/";
|
|
4
5
|
export async function ensureConversationTarget(page, target, options) {
|
|
5
6
|
const targetUrl = absoluteConversationUrl(target);
|
|
7
|
+
requireChatGPTUrl(targetUrl, "Conversation target URL");
|
|
6
8
|
const expectedConversationId = parseConversationId(targetUrl);
|
|
7
9
|
const currentUrl = typeof page.url === "function" ? await Promise.resolve(page.url()).catch(() => "") : "";
|
|
8
|
-
if (
|
|
10
|
+
if (isChatGPTUrl(typeof currentUrl === "string" ? currentUrl : "")
|
|
11
|
+
&&
|
|
12
|
+
expectedConversationId !== undefined
|
|
9
13
|
&& parseConversationId(typeof currentUrl === "string" ? currentUrl : "") === expectedConversationId) {
|
|
10
14
|
await waitForConversationHydrated(page, options.timeoutMs, expectedConversationId);
|
|
11
15
|
return ensureResult(false, targetUrl, expectedConversationId);
|
|
12
16
|
}
|
|
13
17
|
await page.goto?.(targetUrl, { waitUntil: "domcontentloaded", timeout: options.timeoutMs });
|
|
18
|
+
const navigatedUrl = typeof page.url === "function" ? await Promise.resolve(page.url()).catch(() => "") : "";
|
|
19
|
+
requireChatGPTUrl(typeof navigatedUrl === "string" ? navigatedUrl : "", "Conversation navigation result");
|
|
14
20
|
await waitForConversationHydrated(page, options.timeoutMs, expectedConversationId);
|
|
21
|
+
const finalUrl = typeof page.url === "function" ? await Promise.resolve(page.url()).catch(() => "") : "";
|
|
22
|
+
requireChatGPTUrl(typeof finalUrl === "string" ? finalUrl : "", "Hydrated conversation URL");
|
|
23
|
+
if (expectedConversationId !== undefined
|
|
24
|
+
&& parseConversationId(typeof finalUrl === "string" ? finalUrl : "") !== expectedConversationId) {
|
|
25
|
+
throw new Error(`Visible Chat navigation did not reach conversation ${expectedConversationId}.`);
|
|
26
|
+
}
|
|
15
27
|
return ensureResult(true, targetUrl, expectedConversationId);
|
|
16
28
|
}
|
|
17
29
|
export async function waitForConversationHydrated(page, timeoutMs, expectedConversationId) {
|