codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.4
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 +42 -0
- package/README.md +43 -0
- package/contracts/v1/fixtures/backend-capabilities.json +40 -1
- package/contracts/v1/fixtures/backend-compatibility.json +21 -0
- package/contracts/v1/fixtures/backend-version.json +6 -1
- package/contracts/v1/fixtures/download-blocked-by-browser.json +22 -0
- package/contracts/v1/fixtures/download-receipt-failed.json +22 -0
- package/contracts/v1/fixtures/download-receipt-timeout.json +22 -0
- package/contracts/v1/fixtures/journal-rpc-indeterminate.json +32 -0
- package/contracts/v1/fixtures/journal-rpc-unsupported-platform.json +32 -0
- package/contracts/v1/fixtures/journal-runtime-unavailable.json +32 -0
- package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
- package/contracts/v1/fixtures/operation-action.json +13 -0
- package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
- package/contracts/v1/fixtures/operation-blocker.json +10 -0
- package/contracts/v1/fixtures/operation-collect-request.json +17 -0
- package/contracts/v1/fixtures/operation-collect-result.json +42 -0
- package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
- package/contracts/v1/fixtures/operation-control-request.json +18 -0
- package/contracts/v1/fixtures/operation-control-result.json +31 -0
- package/contracts/v1/fixtures/operation-event.json +18 -0
- package/contracts/v1/fixtures/operation-handle.json +10 -0
- package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
- package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
- package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
- package/contracts/v1/fixtures/operation-receipt.json +32 -0
- package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
- package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
- package/contracts/v1/fixtures/operation-request.json +33 -0
- package/contracts/v1/fixtures/operation-state.json +144 -0
- package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
- package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
- package/contracts/v1/fixtures/operation-submit-result.json +16 -0
- package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
- package/contracts/v1/manifest.json +185 -1
- package/contracts/v1/parity-suite.json +106 -8
- package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
- package/contracts/v1/schemas/backend-request.schema.json +8 -2
- package/contracts/v1/schemas/capabilities.schema.json +81 -1
- package/contracts/v1/schemas/manifest.schema.json +57 -0
- package/contracts/v1/schemas/operation-action.schema.json +155 -0
- package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
- package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
- package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
- package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
- package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
- package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
- package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
- package/contracts/v1/schemas/operation-event.schema.json +756 -0
- package/contracts/v1/schemas/operation-handle.schema.json +55 -0
- package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
- package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
- package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
- package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
- package/contracts/v1/schemas/operation-request.schema.json +158 -0
- package/contracts/v1/schemas/operation-state.schema.json +691 -0
- package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
- package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
- package/contracts/v1/surface-drift-policy.json +50 -0
- package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
- package/dist/codex-chatgpt-control-backend.mjs +45882 -9361
- package/dist/codex-chatgpt-control-journal.mjs +5547 -0
- package/dist/codex-chatgpt-control-live-smoke.bundle.mjs +51677 -0
- package/dist/codex-chatgpt-control-release-canary.bundle.mjs +52104 -0
- package/dist/codex-chatgpt-control.bundle.mjs +48392 -10260
- package/dist/src/backend/client.d.ts +103 -2
- package/dist/src/backend/client.js +1436 -97
- package/dist/src/backend/compatibility.d.ts +12 -0
- package/dist/src/backend/compatibility.js +208 -0
- package/dist/src/backend/protocol.d.ts +84 -1
- package/dist/src/backend/protocol.js +31 -5
- package/dist/src/backend/runtime-identity.d.ts +10 -0
- package/dist/src/backend/runtime-identity.js +141 -0
- package/dist/src/backend/session.d.ts +10 -2
- package/dist/src/backend/session.js +478 -7
- package/dist/src/backend/stdio-server.d.ts +4 -1
- package/dist/src/backend/stdio-server.js +240 -28
- package/dist/src/browser/active-composer-file-input.d.ts +9 -0
- package/dist/src/browser/active-composer-file-input.js +33 -0
- package/dist/src/browser/attach.d.ts +4 -1
- package/dist/src/browser/attach.js +136 -28
- package/dist/src/browser/chatgpt-url.d.ts +2 -0
- package/dist/src/browser/chatgpt-url.js +9 -0
- package/dist/src/browser/downloads.d.ts +17 -1
- package/dist/src/browser/downloads.js +150 -28
- package/dist/src/browser/page-state.js +6 -0
- package/dist/src/client.d.ts +43 -0
- package/dist/src/client.js +1279 -59
- package/dist/src/commands/artifacts.js +6 -4
- package/dist/src/commands/chat-popover.d.ts +37 -0
- package/dist/src/commands/chat-popover.js +537 -0
- package/dist/src/commands/configuration.d.ts +5 -0
- package/dist/src/commands/configuration.js +134 -21
- package/dist/src/commands/context.js +10 -3
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +27 -4
- package/dist/src/commands/experience.d.ts +5 -0
- package/dist/src/commands/experience.js +104 -21
- package/dist/src/commands/files.js +14 -33
- package/dist/src/commands/modes.js +85 -21
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +669 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +19 -6
- package/dist/src/commands/work.js +31 -0
- package/dist/src/dom/composer-text.d.ts +6 -0
- package/dist/src/dom/composer-text.js +85 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +9 -0
- package/dist/src/operations/artifact-output.d.ts +69 -0
- package/dist/src/operations/artifact-output.js +1748 -0
- package/dist/src/operations/artifact-stream.d.ts +19 -0
- package/dist/src/operations/artifact-stream.js +35 -0
- package/dist/src/operations/artifact-transfer.d.ts +149 -0
- package/dist/src/operations/artifact-transfer.js +1312 -0
- package/dist/src/operations/browser-adapter.d.ts +167 -0
- package/dist/src/operations/browser-adapter.js +2103 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1256 -0
- package/dist/src/operations/browser-target.d.ts +105 -0
- package/dist/src/operations/browser-target.js +531 -0
- package/dist/src/operations/canonical.d.ts +4 -0
- package/dist/src/operations/canonical.js +412 -0
- package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
- package/dist/src/operations/chatgpt-runtime.js +1232 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1145 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/configuration-routing.d.ts +4 -0
- package/dist/src/operations/configuration-routing.js +14 -0
- package/dist/src/operations/control.d.ts +372 -0
- package/dist/src/operations/control.js +1498 -0
- package/dist/src/operations/file-identity.d.ts +33 -0
- package/dist/src/operations/file-identity.js +153 -0
- package/dist/src/operations/handle.d.ts +19 -0
- package/dist/src/operations/handle.js +727 -0
- package/dist/src/operations/index.d.ts +25 -0
- package/dist/src/operations/index.js +25 -0
- package/dist/src/operations/journal-authority.d.ts +12 -0
- package/dist/src/operations/journal-authority.js +1 -0
- package/dist/src/operations/journal-rpc-client.d.ts +12 -0
- package/dist/src/operations/journal-rpc-client.js +225 -0
- package/dist/src/operations/journal-rpc-protocol.d.ts +32 -0
- package/dist/src/operations/journal-rpc-protocol.js +214 -0
- package/dist/src/operations/journal-rpc-server.d.ts +15 -0
- package/dist/src/operations/journal-rpc-server.js +272 -0
- package/dist/src/operations/journal.d.ts +143 -0
- package/dist/src/operations/journal.js +1957 -0
- package/dist/src/operations/production-attachments.d.ts +118 -0
- package/dist/src/operations/production-attachments.js +1035 -0
- package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
- package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
- package/dist/src/operations/production-chatgpt-attachments.d.ts +80 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1639 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1544 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +856 -0
- package/dist/src/operations/production-work-steer.d.ts +203 -0
- package/dist/src/operations/production-work-steer.js +1218 -0
- package/dist/src/operations/recovery.d.ts +80 -0
- package/dist/src/operations/recovery.js +174 -0
- package/dist/src/operations/runtime-adapter.d.ts +126 -0
- package/dist/src/operations/runtime-adapter.js +802 -0
- package/dist/src/operations/send-once.d.ts +224 -0
- package/dist/src/operations/send-once.js +1081 -0
- package/dist/src/operations/service.d.ts +286 -0
- package/dist/src/operations/service.js +2831 -0
- package/dist/src/operations/staging.d.ts +136 -0
- package/dist/src/operations/staging.js +630 -0
- package/dist/src/operations/state-machine.d.ts +29 -0
- package/dist/src/operations/state-machine.js +2047 -0
- package/dist/src/operations/submission.d.ts +423 -0
- package/dist/src/operations/submission.js +1676 -0
- package/dist/src/operations/transactional-chat-power.d.ts +17 -0
- package/dist/src/operations/transactional-chat-power.js +212 -0
- package/dist/src/operations/turn-ownership.d.ts +202 -0
- package/dist/src/operations/turn-ownership.js +700 -0
- package/dist/src/operations/types.d.ts +454 -0
- package/dist/src/operations/types.js +20 -0
- package/dist/src/operations/wire-requests.d.ts +21 -0
- package/dist/src/operations/wire-requests.js +396 -0
- package/dist/src/operations/wire-results.d.ts +97 -0
- package/dist/src/operations/wire-results.js +818 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +19 -1
- package/dist/src/runner/result.js +227 -55
- package/dist/src/runner/types.d.ts +12 -0
- package/dist/src/runtime/command-routing.d.ts +149 -0
- package/dist/src/runtime/command-routing.js +431 -0
- package/dist/src/runtime/coordinated-browser.d.ts +24 -0
- package/dist/src/runtime/coordinated-browser.js +315 -0
- package/dist/src/runtime/coordinated-page.d.ts +41 -0
- package/dist/src/runtime/coordinated-page.js +651 -0
- package/dist/src/runtime/operation-context.d.ts +142 -0
- package/dist/src/runtime/operation-context.js +410 -0
- package/dist/src/runtime/runtime-session.d.ts +95 -0
- package/dist/src/runtime/runtime-session.js +314 -0
- package/dist/src/runtime/tab-coordinator.d.ts +200 -0
- package/dist/src/runtime/tab-coordinator.js +1200 -0
- package/dist/src/runtime/value-boundaries.d.ts +18 -0
- package/dist/src/runtime/value-boundaries.js +49 -0
- package/dist/src/safety/blockers.js +8 -1
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/capture-surface-profile.js +7 -2
- package/dist/src/scripts/journal-server.d.ts +2 -0
- package/dist/src/scripts/journal-server.js +37 -0
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +42 -1
- package/dist/src/scripts/live-smoke/scenarios.js +147 -30
- package/dist/src/scripts/live-smoke/transactional.d.ts +17 -0
- package/dist/src/scripts/live-smoke/transactional.js +254 -0
- package/dist/src/scripts/live-smoke/types.d.ts +3 -0
- package/dist/src/scripts/release-canary-module.js +17 -4
- package/dist/src/types.d.ts +38 -1
- package/package.json +8 -3
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/2026-09-06-journal-service.md +140 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +176 -0
- package/references/python-parity.md +137 -0
- package/references/responses-adapter.md +19 -0
- package/references/streaming.md +6 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { copyFile, mkdir, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
|
-
import { waitForDownloadFromClick } from "../browser/downloads.js";
|
|
3
|
+
import { isTerminalDownloadCode, isTerminalDownloadError, waitForDownloadFromClick } from "../browser/downloads.js";
|
|
4
4
|
import { readPageState } from "../browser/page-state.js";
|
|
5
5
|
import { listPageArtifacts, readLatestImageDataUrl } from "../dom/artifacts.js";
|
|
6
6
|
import { cssSelectors, requiredLocator } from "../dom/selectors.js";
|
|
7
7
|
import { localeLabels } from "../dom/locale-labels.js";
|
|
8
|
-
import { resultOk } from "../errors.js";
|
|
8
|
+
import { resultError, resultOk } from "../errors.js";
|
|
9
9
|
import { contextFromPage } from "./context.js";
|
|
10
10
|
import { ensurePage } from "./session.js";
|
|
11
11
|
import { localGuardTimeout, withTimeout } from "./timeouts.js";
|
|
@@ -112,7 +112,7 @@ export async function downloadLatestArtifact(env, args) {
|
|
|
112
112
|
const timeoutMs = args.timeoutMs ?? 120000;
|
|
113
113
|
if (args.prefer !== "visible_image_source") {
|
|
114
114
|
const byDownload = await tryDownloadControl(page, args, timeoutMs);
|
|
115
|
-
if (byDownload.ok || args.prefer === "download_control") {
|
|
115
|
+
if (byDownload.ok || isTerminalDownloadCode(byDownload.blocker?.code) || args.prefer === "download_control") {
|
|
116
116
|
return byDownload;
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -156,7 +156,7 @@ async function tryDownloadControl(page, args, timeoutMs) {
|
|
|
156
156
|
return resultOk(downloaded, await contextFromPage(page));
|
|
157
157
|
}
|
|
158
158
|
catch (error) {
|
|
159
|
-
return artifactDownloadBlocker(error, await contextFromPage(page));
|
|
159
|
+
return artifactDownloadBlocker(error, await contextFromPage(page, {}, { minimal: isTerminalDownloadError(error) }));
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
async function saveLatestVisibleImageSource(page, destDir, timeoutMs) {
|
|
@@ -395,6 +395,8 @@ function artifactSelectorBlocker(error, context) {
|
|
|
395
395
|
};
|
|
396
396
|
}
|
|
397
397
|
function artifactDownloadBlocker(error, context) {
|
|
398
|
+
if (isTerminalDownloadError(error))
|
|
399
|
+
return resultError(error, context);
|
|
398
400
|
return {
|
|
399
401
|
ok: false,
|
|
400
402
|
status: "unsupported",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PageLike } from "../types.js";
|
|
2
|
+
export type ChatPopoverSnapshot = {
|
|
3
|
+
view: "simple" | "advanced";
|
|
4
|
+
rootIndex: number;
|
|
5
|
+
triggerId?: string;
|
|
6
|
+
toggleIndex: number;
|
|
7
|
+
modelOptions: Array<{
|
|
8
|
+
label: string;
|
|
9
|
+
checked: boolean;
|
|
10
|
+
index: number;
|
|
11
|
+
}>;
|
|
12
|
+
activeModel?: string;
|
|
13
|
+
effort?: string;
|
|
14
|
+
speed?: "Standard" | "Fast";
|
|
15
|
+
speedIndex?: number;
|
|
16
|
+
slider?: {
|
|
17
|
+
index: number;
|
|
18
|
+
minimum: number;
|
|
19
|
+
maximum: number;
|
|
20
|
+
current: number;
|
|
21
|
+
valueText?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
/** Inactive model radios retain geometry. Require an owned, active, non-inert view. */
|
|
25
|
+
export declare function readChatPopover(page: PageLike): Promise<{
|
|
26
|
+
snapshot?: ChatPopoverSnapshot;
|
|
27
|
+
acted: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function closeChatPopover(page: PageLike, before?: ChatPopoverSnapshot): Promise<boolean>;
|
|
30
|
+
export declare function setChatPopoverView(page: PageLike, view: ChatPopoverSnapshot["view"]): Promise<ChatPopoverSnapshot | undefined>;
|
|
31
|
+
/** Opens the model view for evidence; does not probe slider values during inspection. */
|
|
32
|
+
export declare function inspectChatPopover(page: PageLike): Promise<ChatPopoverSnapshot | undefined>;
|
|
33
|
+
export declare function selectChatPopoverModel(page: PageLike, labels: string[]): Promise<string | undefined>;
|
|
34
|
+
/** Search the observed bounded range during mutation, restoring an unmatched search. */
|
|
35
|
+
export declare function selectChatPopoverEffort(page: PageLike, labels: string[]): Promise<string | undefined>;
|
|
36
|
+
/** The optional Work fast-mode checkbox is independent of model and effort. */
|
|
37
|
+
export declare function selectChatPopoverSpeed(page: PageLike, labels: string[]): Promise<string | undefined>;
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { localeLabels } from "../dom/locale-labels.js";
|
|
2
|
+
import { normalizeForLabelMatch } from "../dom/label-match.js";
|
|
3
|
+
/** Inactive model radios retain geometry. Require an owned, active, non-inert view. */
|
|
4
|
+
export async function readChatPopover(page) {
|
|
5
|
+
if (page.evaluate === undefined)
|
|
6
|
+
return { acted: false };
|
|
7
|
+
const observation = await page.evaluate((config) => {
|
|
8
|
+
const visible = (element, allowSliderAriaHidden = false) => {
|
|
9
|
+
let current = element;
|
|
10
|
+
for (let depth = 0; current !== null && depth < 64; depth += 1) {
|
|
11
|
+
if (current.nodeType !== 1)
|
|
12
|
+
return true;
|
|
13
|
+
const node = current;
|
|
14
|
+
if (node.hidden || node.hasAttribute("hidden") || node.hasAttribute("inert")
|
|
15
|
+
|| node.getAttribute("data-active") === "false"
|
|
16
|
+
|| (node.getAttribute("aria-hidden") === "true" && !(current === element && allowSliderAriaHidden))
|
|
17
|
+
|| node.getAttribute("aria-disabled") === "true" || node.getAttribute("data-locked") === "true")
|
|
18
|
+
return false;
|
|
19
|
+
const style = window.getComputedStyle(node);
|
|
20
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0" || style.pointerEvents === "none")
|
|
21
|
+
return false;
|
|
22
|
+
const rect = node.getBoundingClientRect();
|
|
23
|
+
if (current === element && (rect.width <= 0 || rect.height <= 0))
|
|
24
|
+
return false;
|
|
25
|
+
current = current.parentNode;
|
|
26
|
+
}
|
|
27
|
+
return current === null;
|
|
28
|
+
};
|
|
29
|
+
const normalize = (value) => value.replace(/\s+/g, " ").trim();
|
|
30
|
+
const roots = document.querySelectorAll('[data-testid="composer-intelligence-picker-content"]');
|
|
31
|
+
if (roots.length > 8)
|
|
32
|
+
return { acted: false };
|
|
33
|
+
const activeRoots = Array.from(roots).filter(node => visible(node));
|
|
34
|
+
if (activeRoots.length !== 1)
|
|
35
|
+
return { acted: false };
|
|
36
|
+
const root = activeRoots[0];
|
|
37
|
+
let menu = null;
|
|
38
|
+
let ancestor = root.parentNode;
|
|
39
|
+
for (let depth = 0; ancestor?.nodeType === 1 && depth < 16; depth += 1) {
|
|
40
|
+
const element = ancestor;
|
|
41
|
+
if (element.getAttribute("role") === "menu") {
|
|
42
|
+
menu = element;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
ancestor = ancestor.parentNode;
|
|
46
|
+
}
|
|
47
|
+
if (menu?.getAttribute("data-state") !== "open" || !visible(menu))
|
|
48
|
+
return { acted: false };
|
|
49
|
+
const nodes = [];
|
|
50
|
+
const text = new Map();
|
|
51
|
+
let current = root.firstChild;
|
|
52
|
+
let count = 0;
|
|
53
|
+
let chars = 0;
|
|
54
|
+
while (current !== null) {
|
|
55
|
+
if (++count > 4096)
|
|
56
|
+
return { acted: false };
|
|
57
|
+
if (current.nodeType === 1)
|
|
58
|
+
nodes.push(current);
|
|
59
|
+
if (current.nodeType === 3) {
|
|
60
|
+
const value = current.nodeValue ?? "";
|
|
61
|
+
chars += value.length;
|
|
62
|
+
if (chars > 32768)
|
|
63
|
+
return { acted: false };
|
|
64
|
+
let parent = current.parentNode;
|
|
65
|
+
for (let depth = 0; parent !== null && parent !== root && depth < 64; depth += 1) {
|
|
66
|
+
if (parent.nodeType === 1)
|
|
67
|
+
text.set(parent, ((text.get(parent) ?? "") + value).slice(0, 240));
|
|
68
|
+
parent = parent.parentNode;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (current.firstChild !== null) {
|
|
72
|
+
current = current.firstChild;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
while (current !== null && current !== root && current.nextSibling === null)
|
|
76
|
+
current = current.parentNode;
|
|
77
|
+
if (current === root || current === null)
|
|
78
|
+
break;
|
|
79
|
+
current = current.nextSibling;
|
|
80
|
+
}
|
|
81
|
+
const within = (node, owner) => {
|
|
82
|
+
let current = node;
|
|
83
|
+
for (let depth = 0; current !== null && depth < 64; depth += 1) {
|
|
84
|
+
if (current === owner)
|
|
85
|
+
return true;
|
|
86
|
+
current = current.parentNode;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
const owners = nodes.filter(node => node.getAttribute("data-has-slider") === "true"
|
|
91
|
+
&& node.getAttribute("data-has-advanced-view") === "true"
|
|
92
|
+
&& node.getAttribute("data-model-selection-view") === "true" && visible(node));
|
|
93
|
+
if (owners.length !== 1)
|
|
94
|
+
return { acted: false };
|
|
95
|
+
const owner = owners[0];
|
|
96
|
+
const view = owner.getAttribute("data-view");
|
|
97
|
+
if (view !== "simple" && view !== "advanced")
|
|
98
|
+
return { acted: false };
|
|
99
|
+
const panels = nodes.filter(node => node.getAttribute("data-testid") === `composer-model-picker-slider-${view}-view`
|
|
100
|
+
&& within(node, owner) && node.getAttribute("data-active") === "true" && visible(node));
|
|
101
|
+
if (panels.length !== 1)
|
|
102
|
+
return { acted: false };
|
|
103
|
+
const panel = panels[0];
|
|
104
|
+
const toggles = nodes.filter(node => node.getAttribute("role") === "menuitem"
|
|
105
|
+
&& node.getAttribute("data-interactive") === "true"
|
|
106
|
+
&& node.getAttribute("aria-expanded") === String(view === "advanced")
|
|
107
|
+
&& within(node, owner) && visible(node));
|
|
108
|
+
if (toggles.length > 1 || (view === "simple" && toggles.length !== 1))
|
|
109
|
+
return { acted: false };
|
|
110
|
+
// The structural toggle owns this transition, independently of localized name/text.
|
|
111
|
+
const modelNodes = view === "advanced" ? nodes.filter(node => within(node, panel)
|
|
112
|
+
&& node.getAttribute("role") === "menuitemradio" && visible(node)) : [];
|
|
113
|
+
if (modelNodes.length > 32)
|
|
114
|
+
return { acted: false };
|
|
115
|
+
const modelLabel = (node) => {
|
|
116
|
+
const primary = nodes.filter(child => within(child, node) && (child.getAttribute("class") ?? "").split(/\s+/).includes("truncate"));
|
|
117
|
+
return normalize(primary.length === 1 ? text.get(primary[0]) ?? "" : text.get(node) ?? "");
|
|
118
|
+
};
|
|
119
|
+
const snapshot = {
|
|
120
|
+
view, rootIndex: Array.from(roots).indexOf(root),
|
|
121
|
+
toggleIndex: nodes.filter(node => node.getAttribute("role") === "menuitem" && node.getAttribute("data-interactive") === "true").indexOf(toggles[0]), modelOptions: modelNodes.map(node => ({
|
|
122
|
+
label: modelLabel(node), checked: node.getAttribute("aria-checked") === "true",
|
|
123
|
+
index: nodes.filter(row => within(row, panel) && row.getAttribute("role") === "menuitemradio").indexOf(node)
|
|
124
|
+
})).filter(option => option.label.length > 0)
|
|
125
|
+
};
|
|
126
|
+
const triggerId = menu.getAttribute("aria-labelledby");
|
|
127
|
+
if (triggerId !== null && triggerId.length > 0 && triggerId.length < 240 && !/\s/.test(triggerId))
|
|
128
|
+
snapshot.triggerId = triggerId;
|
|
129
|
+
const checked = snapshot.modelOptions.filter(option => option.checked);
|
|
130
|
+
if (checked.length === 1)
|
|
131
|
+
snapshot.activeModel = checked[0].label;
|
|
132
|
+
if (view === "simple") {
|
|
133
|
+
const speedControls = nodes.filter(node => node.getAttribute("role") === "menuitemcheckbox"
|
|
134
|
+
&& node.hasAttribute("data-fast-mode-enabled") && within(node, owner) && visible(node));
|
|
135
|
+
if (speedControls.length === 1) {
|
|
136
|
+
const speed = speedControls[0];
|
|
137
|
+
const checked = speed.getAttribute("aria-checked");
|
|
138
|
+
if ((checked === "true" || checked === "false") && speed.getAttribute("data-fast-mode-enabled") === checked
|
|
139
|
+
&& speed.getAttribute("data-visible") === "true") {
|
|
140
|
+
snapshot.speed = checked === "true" ? "Fast" : "Standard";
|
|
141
|
+
snapshot.speedIndex = nodes.filter(node => node.getAttribute("role") === "menuitemcheckbox" && node.hasAttribute("data-fast-mode-enabled")).indexOf(speed);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const sliders = nodes.filter(node => node.getAttribute("role") === "slider" && within(node, panel) && visible(node, true));
|
|
145
|
+
if (sliders.length === 1) {
|
|
146
|
+
const slider = sliders[0];
|
|
147
|
+
const powers = nodes.filter(node => within(slider, node) && within(node, panel)
|
|
148
|
+
&& node.getAttribute("role") === "menuitem" && visible(node)
|
|
149
|
+
&& config.powerLabels.some(label => normalize(node.getAttribute("aria-label") ?? "").toLocaleLowerCase() === label.toLocaleLowerCase()));
|
|
150
|
+
const sliderOwners = nodes.filter(node => within(slider, node) && within(node, panel) && node.hasAttribute("data-model-reasoning-effort-slider"));
|
|
151
|
+
const integer = (name) => {
|
|
152
|
+
const raw = slider.getAttribute(name);
|
|
153
|
+
return raw !== null && /^-?\d+$/.test(raw) && Number.isSafeInteger(Number(raw)) ? Number(raw) : undefined;
|
|
154
|
+
};
|
|
155
|
+
const minimum = integer("aria-valuemin"), maximum = integer("aria-valuemax"), now = integer("aria-valuenow");
|
|
156
|
+
if (powers.length === 1 && sliderOwners.length === 1 && minimum !== undefined && maximum !== undefined && now !== undefined
|
|
157
|
+
&& maximum > minimum && maximum - minimum < 32 && now >= minimum && now <= maximum) {
|
|
158
|
+
snapshot.slider = { index: nodes.filter(node => node.getAttribute("role") === "slider" && within(node, panel)).indexOf(slider), minimum, maximum, current: now };
|
|
159
|
+
const valueText = normalize(slider.getAttribute("aria-valuetext") ?? "");
|
|
160
|
+
if (valueText.length > 0)
|
|
161
|
+
snapshot.slider.valueText = valueText;
|
|
162
|
+
else {
|
|
163
|
+
const descriptionIds = (powers[0].getAttribute("aria-describedby") ?? "").split(/\s+/).slice(0, 8);
|
|
164
|
+
for (const id of descriptionIds) {
|
|
165
|
+
const descriptions = nodes.filter(node => node.getAttribute("id") === id);
|
|
166
|
+
if (descriptions.length !== 1)
|
|
167
|
+
continue;
|
|
168
|
+
const description = normalize(text.get(descriptions[0]) ?? "");
|
|
169
|
+
const ordinal = /^(.+?),\s*(\d+)\s+of\s+(\d+)\./u.exec(description);
|
|
170
|
+
if (ordinal !== null && Number(ordinal[2]) === now - minimum + 1 && Number(ordinal[3]) === maximum - minimum + 1)
|
|
171
|
+
snapshot.slider.valueText = ordinal[1];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (snapshot.slider.valueText !== undefined)
|
|
175
|
+
snapshot.effort = snapshot.slider.valueText;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return { snapshot, acted: false };
|
|
180
|
+
}, { powerLabels: localeLabels.configurationAxes.power }).catch(() => ({ acted: false }));
|
|
181
|
+
return observation !== undefined && observation !== null && typeof observation.acted === "boolean" ? observation : { acted: false };
|
|
182
|
+
}
|
|
183
|
+
function observedRoot(page, snapshot) {
|
|
184
|
+
const roots = page.locator?.('[data-testid="composer-intelligence-picker-content"]');
|
|
185
|
+
return roots?.nth?.(snapshot.rootIndex) ?? (snapshot.rootIndex === 0 ? roots : undefined);
|
|
186
|
+
}
|
|
187
|
+
async function clickObservedControl(page, snapshot, modelIndex) {
|
|
188
|
+
const selector = modelIndex === undefined ? '[role="menuitem"][data-interactive="true"]'
|
|
189
|
+
: '[data-testid="composer-model-picker-slider-advanced-view"][data-active="true"] [role="menuitemradio"]';
|
|
190
|
+
const candidates = observedRoot(page, snapshot)?.locator?.(selector);
|
|
191
|
+
const index = modelIndex ?? snapshot.toggleIndex;
|
|
192
|
+
const target = candidates?.nth?.(index) ?? (index === 0 ? candidates : undefined);
|
|
193
|
+
if (target?.click === undefined || target.evaluate === undefined || await target.count?.() !== 1)
|
|
194
|
+
return false;
|
|
195
|
+
const state = await target.evaluate(element => {
|
|
196
|
+
let current = element;
|
|
197
|
+
let owned = false;
|
|
198
|
+
for (let depth = 0; current !== null && depth < 64; depth += 1) {
|
|
199
|
+
if (current.nodeType !== 1)
|
|
200
|
+
break;
|
|
201
|
+
const node = current;
|
|
202
|
+
if (node.hidden || node.hasAttribute("hidden") || node.hasAttribute("inert") || node.getAttribute("aria-hidden") === "true"
|
|
203
|
+
|| node.getAttribute("aria-disabled") === "true" || node.getAttribute("data-active") === "false")
|
|
204
|
+
return undefined;
|
|
205
|
+
const style = window.getComputedStyle(node);
|
|
206
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0" || style.pointerEvents === "none")
|
|
207
|
+
return undefined;
|
|
208
|
+
if (node.getAttribute("data-testid") === "composer-intelligence-picker-content")
|
|
209
|
+
owned = true;
|
|
210
|
+
current = current.parentNode;
|
|
211
|
+
}
|
|
212
|
+
const rect = element.getBoundingClientRect();
|
|
213
|
+
if (!owned || rect.width <= 0 || rect.height <= 0)
|
|
214
|
+
return undefined;
|
|
215
|
+
let label = "";
|
|
216
|
+
const primary = new Map();
|
|
217
|
+
let child = element.firstChild;
|
|
218
|
+
let count = 0;
|
|
219
|
+
while (child !== null) {
|
|
220
|
+
if (++count > 512)
|
|
221
|
+
return undefined;
|
|
222
|
+
if (child.nodeType === 3) {
|
|
223
|
+
label += child.nodeValue ?? "";
|
|
224
|
+
if (label.length > 512)
|
|
225
|
+
return undefined;
|
|
226
|
+
let parent = child.parentNode;
|
|
227
|
+
for (let depth = 0; parent !== null && parent !== element && depth < 64; depth += 1) {
|
|
228
|
+
if (parent.nodeType === 1 && (parent.getAttribute("class") ?? "").split(/\s+/).includes("truncate")) {
|
|
229
|
+
primary.set(parent, (primary.get(parent) ?? "") + (child.nodeValue ?? ""));
|
|
230
|
+
}
|
|
231
|
+
parent = parent.parentNode;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (child.firstChild !== null) {
|
|
235
|
+
child = child.firstChild;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
while (child !== null && child !== element && child.nextSibling === null)
|
|
239
|
+
child = child.parentNode;
|
|
240
|
+
if (child === element || child === null)
|
|
241
|
+
break;
|
|
242
|
+
child = child.nextSibling;
|
|
243
|
+
}
|
|
244
|
+
return { role: element.getAttribute("role"), expanded: element.getAttribute("aria-expanded"), label: (primary.size === 1 ? [...primary.values()][0] : label).replace(/\s+/g, " ").trim() };
|
|
245
|
+
}).catch(() => undefined);
|
|
246
|
+
if (state === undefined || state.role !== (modelIndex === undefined ? "menuitem" : "menuitemradio")
|
|
247
|
+
|| (modelIndex === undefined && state.expanded !== String(snapshot.view === "advanced")))
|
|
248
|
+
return false;
|
|
249
|
+
const expectedModel = snapshot.modelOptions.find(option => option.index === modelIndex);
|
|
250
|
+
if (modelIndex !== undefined && (expectedModel === undefined || state.label !== expectedModel.label))
|
|
251
|
+
return false;
|
|
252
|
+
await target.click();
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
export async function closeChatPopover(page, before) {
|
|
256
|
+
const snapshot = before ?? (await readChatPopover(page)).snapshot;
|
|
257
|
+
if (snapshot === undefined)
|
|
258
|
+
return false;
|
|
259
|
+
const escapedId = snapshot.triggerId?.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
260
|
+
const menu = escapedId === undefined ? undefined : page.locator?.(`[role="menu"][aria-labelledby="${escapedId}"]`);
|
|
261
|
+
if (menu?.press !== undefined && await menu.count?.() === 1)
|
|
262
|
+
await menu.press("Escape");
|
|
263
|
+
else if (page.keyboard?.press !== undefined)
|
|
264
|
+
await page.keyboard.press("Escape");
|
|
265
|
+
else if (page.cua?.keypress !== undefined)
|
|
266
|
+
await page.cua.keypress({ keys: ["ESC"] });
|
|
267
|
+
else
|
|
268
|
+
return false;
|
|
269
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
270
|
+
await waitForPopoverTransition(page, 100);
|
|
271
|
+
if ((await readChatPopover(page)).snapshot === undefined)
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
async function waitForPopoverTransition(page, milliseconds) {
|
|
277
|
+
if (page.waitForTimeout !== undefined)
|
|
278
|
+
await page.waitForTimeout(milliseconds);
|
|
279
|
+
else
|
|
280
|
+
await new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
281
|
+
}
|
|
282
|
+
async function reopenChatPopover(page, before) {
|
|
283
|
+
if (before.triggerId === undefined)
|
|
284
|
+
return undefined;
|
|
285
|
+
if (!await closeChatPopover(page, before))
|
|
286
|
+
return undefined;
|
|
287
|
+
// aria-labelledby was read from this exact owned menu before closing. The
|
|
288
|
+
// trigger's accessible name can change when a model view is dismissed.
|
|
289
|
+
const trigger = page.locator?.(`[id="${before.triggerId.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`);
|
|
290
|
+
if (trigger?.click === undefined || trigger.evaluate === undefined || await trigger.count?.() !== 1)
|
|
291
|
+
return undefined;
|
|
292
|
+
const isVisible = () => trigger.evaluate(element => {
|
|
293
|
+
let current = element;
|
|
294
|
+
for (let depth = 0; current !== null && depth < 64; depth += 1) {
|
|
295
|
+
if (current.nodeType !== 1)
|
|
296
|
+
break;
|
|
297
|
+
const node = current;
|
|
298
|
+
if (node.hidden || node.hasAttribute("hidden") || node.hasAttribute("inert") || node.getAttribute("aria-hidden") === "true" || node.getAttribute("aria-disabled") === "true")
|
|
299
|
+
return false;
|
|
300
|
+
const style = window.getComputedStyle(node);
|
|
301
|
+
// Work task composers sit below pointer-events:none layout wrappers.
|
|
302
|
+
// A descendant can override that property; computed target actionability
|
|
303
|
+
// remains required, while hidden/inert ancestors still block.
|
|
304
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0"
|
|
305
|
+
|| (current === element && style.pointerEvents === "none"))
|
|
306
|
+
return false;
|
|
307
|
+
current = current.parentNode;
|
|
308
|
+
}
|
|
309
|
+
if (current?.nodeType === 1)
|
|
310
|
+
return false;
|
|
311
|
+
const rect = element.getBoundingClientRect();
|
|
312
|
+
return rect.width > 0 && rect.height > 0 && (element.tagName.toLowerCase() === "button" || element.getAttribute("role") === "button");
|
|
313
|
+
}).catch(() => false);
|
|
314
|
+
let visible = false;
|
|
315
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
316
|
+
if (await isVisible()) {
|
|
317
|
+
visible = true;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
if (attempt + 1 < 5)
|
|
321
|
+
await waitForPopoverTransition(page, 100);
|
|
322
|
+
}
|
|
323
|
+
if (!visible)
|
|
324
|
+
return undefined;
|
|
325
|
+
await trigger.click();
|
|
326
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
327
|
+
await waitForPopoverTransition(page, 100);
|
|
328
|
+
const reopened = (await readChatPopover(page)).snapshot;
|
|
329
|
+
if (reopened?.view === "simple" && reopened.triggerId === before.triggerId)
|
|
330
|
+
return reopened;
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
export async function setChatPopoverView(page, view) {
|
|
335
|
+
const before = (await readChatPopover(page)).snapshot;
|
|
336
|
+
if (before === undefined || before.view === view)
|
|
337
|
+
return before;
|
|
338
|
+
if (view === "simple" && before.triggerId !== undefined)
|
|
339
|
+
return reopenChatPopover(page, before);
|
|
340
|
+
if (!await clickObservedControl(page, before))
|
|
341
|
+
return undefined;
|
|
342
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
343
|
+
await waitForPopoverTransition(page, 100);
|
|
344
|
+
const after = (await readChatPopover(page)).snapshot;
|
|
345
|
+
if (after?.view === view)
|
|
346
|
+
return after;
|
|
347
|
+
}
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
/** Opens the model view for evidence; does not probe slider values during inspection. */
|
|
351
|
+
export async function inspectChatPopover(page) {
|
|
352
|
+
const initial = (await readChatPopover(page)).snapshot;
|
|
353
|
+
if (initial === undefined)
|
|
354
|
+
return undefined;
|
|
355
|
+
let inspected;
|
|
356
|
+
try {
|
|
357
|
+
const simple = await setChatPopoverView(page, "simple");
|
|
358
|
+
const advanced = await setChatPopoverView(page, "advanced");
|
|
359
|
+
if (simple !== undefined && advanced !== undefined) {
|
|
360
|
+
inspected = { ...advanced, view: initial.view, ...(simple.effort === undefined ? {} : { effort: simple.effort }),
|
|
361
|
+
...(simple.slider === undefined ? {} : { slider: simple.slider }),
|
|
362
|
+
...(simple.speed === undefined ? {} : { speed: simple.speed, speedIndex: simple.speedIndex }) };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
if (await setChatPopoverView(page, initial.view) === undefined)
|
|
367
|
+
inspected = undefined;
|
|
368
|
+
}
|
|
369
|
+
return inspected;
|
|
370
|
+
}
|
|
371
|
+
export async function selectChatPopoverModel(page, labels) {
|
|
372
|
+
const initial = (await readChatPopover(page)).snapshot;
|
|
373
|
+
if (initial === undefined)
|
|
374
|
+
return undefined;
|
|
375
|
+
try {
|
|
376
|
+
const advanced = await setChatPopoverView(page, "advanced");
|
|
377
|
+
if (advanced === undefined)
|
|
378
|
+
return undefined;
|
|
379
|
+
const matches = advanced.modelOptions.filter(option => labels.some(label => normalizeForLabelMatch(label) === normalizeForLabelMatch(option.label)));
|
|
380
|
+
if (matches.length !== 1 || !await clickObservedControl(page, advanced, matches[0].index))
|
|
381
|
+
return undefined;
|
|
382
|
+
await waitForPopoverTransition(page, 150);
|
|
383
|
+
const after = await setChatPopoverView(page, "advanced");
|
|
384
|
+
return after?.activeModel !== undefined && labels.some(label => normalizeForLabelMatch(label) === normalizeForLabelMatch(after.activeModel)) ? after.activeModel : undefined;
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
await setChatPopoverView(page, initial.view);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Search the observed bounded range during mutation, restoring an unmatched search. */
|
|
391
|
+
export async function selectChatPopoverEffort(page, labels) {
|
|
392
|
+
const initial = (await readChatPopover(page)).snapshot;
|
|
393
|
+
if (initial === undefined)
|
|
394
|
+
return undefined;
|
|
395
|
+
let original;
|
|
396
|
+
let found = false;
|
|
397
|
+
let mayRestore = true;
|
|
398
|
+
const read = async () => (await readChatPopover(page)).snapshot;
|
|
399
|
+
const move = async (target) => {
|
|
400
|
+
for (let attempt = 0; attempt < 32; attempt += 1) {
|
|
401
|
+
const before = await read();
|
|
402
|
+
if (original === undefined || before?.slider === undefined || before.slider.minimum !== original.minimum || before.slider.maximum !== original.maximum) {
|
|
403
|
+
mayRestore = false;
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
if (before.slider.current === target)
|
|
407
|
+
return before;
|
|
408
|
+
const candidates = observedRoot(page, before)?.locator?.('[data-testid="composer-model-picker-slider-simple-view"][data-active="true"] [role="slider"]');
|
|
409
|
+
const locator = candidates?.nth?.(before.slider.index) ?? (before.slider.index === 0 ? candidates : undefined);
|
|
410
|
+
if (locator?.press === undefined || locator.evaluate === undefined || await locator.count?.() !== 1)
|
|
411
|
+
return undefined;
|
|
412
|
+
const locatorState = await locator.evaluate(element => {
|
|
413
|
+
let current = element;
|
|
414
|
+
let panel = false, owned = false;
|
|
415
|
+
for (let depth = 0; current !== null && depth < 64; depth += 1) {
|
|
416
|
+
if (current.nodeType !== 1)
|
|
417
|
+
break;
|
|
418
|
+
const node = current;
|
|
419
|
+
if (node.hidden || node.hasAttribute("hidden") || node.hasAttribute("inert") || node.getAttribute("data-active") === "false"
|
|
420
|
+
|| node.getAttribute("aria-disabled") === "true" || node.getAttribute("data-locked") === "true"
|
|
421
|
+
|| (current !== element && node.getAttribute("aria-hidden") === "true"))
|
|
422
|
+
return undefined;
|
|
423
|
+
const style = window.getComputedStyle(node);
|
|
424
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0" || style.pointerEvents === "none")
|
|
425
|
+
return undefined;
|
|
426
|
+
if (node.getAttribute("data-testid") === "composer-model-picker-slider-simple-view" && node.getAttribute("data-active") === "true")
|
|
427
|
+
panel = true;
|
|
428
|
+
if (node.getAttribute("data-testid") === "composer-intelligence-picker-content")
|
|
429
|
+
owned = true;
|
|
430
|
+
current = current.parentNode;
|
|
431
|
+
}
|
|
432
|
+
const rect = element.getBoundingClientRect();
|
|
433
|
+
if (!panel || !owned || rect.width <= 0 || rect.height <= 0 || element.getAttribute("role") !== "slider")
|
|
434
|
+
return undefined;
|
|
435
|
+
return { minimum: element.getAttribute("aria-valuemin"), maximum: element.getAttribute("aria-valuemax"), current: element.getAttribute("aria-valuenow") };
|
|
436
|
+
}).catch(() => undefined);
|
|
437
|
+
if (locatorState === undefined || locatorState.minimum !== String(before.slider.minimum)
|
|
438
|
+
|| locatorState.maximum !== String(before.slider.maximum) || locatorState.current !== String(before.slider.current))
|
|
439
|
+
return undefined;
|
|
440
|
+
const delta = target > before.slider.current ? 1 : -1;
|
|
441
|
+
mayRestore = false;
|
|
442
|
+
await locator.press(delta > 0 ? "ArrowRight" : "ArrowLeft");
|
|
443
|
+
await waitForPopoverTransition(page, 100);
|
|
444
|
+
const after = await read();
|
|
445
|
+
if (after?.slider === undefined || after.slider.minimum !== original.minimum || after.slider.maximum !== original.maximum || after.slider.current !== before.slider.current + delta)
|
|
446
|
+
return undefined;
|
|
447
|
+
mayRestore = true;
|
|
448
|
+
}
|
|
449
|
+
return undefined;
|
|
450
|
+
};
|
|
451
|
+
try {
|
|
452
|
+
const simple = await setChatPopoverView(page, "simple");
|
|
453
|
+
original = simple?.slider;
|
|
454
|
+
if (original === undefined || simple?.effort === undefined)
|
|
455
|
+
return undefined;
|
|
456
|
+
const matches = (snapshot) => snapshot?.effort !== undefined
|
|
457
|
+
&& labels.some(label => normalizeForLabelMatch(label) === normalizeForLabelMatch(snapshot.effort)) ? snapshot.effort : undefined;
|
|
458
|
+
const noop = matches(simple);
|
|
459
|
+
if (noop !== undefined) {
|
|
460
|
+
found = true;
|
|
461
|
+
return noop;
|
|
462
|
+
}
|
|
463
|
+
for (let value = original.minimum; value <= original.maximum; value += 1) {
|
|
464
|
+
const observed = await move(value);
|
|
465
|
+
if (observed === undefined || observed.effort === undefined)
|
|
466
|
+
return undefined;
|
|
467
|
+
const matched = matches(observed);
|
|
468
|
+
if (matched !== undefined) {
|
|
469
|
+
found = true;
|
|
470
|
+
return matched;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
finally {
|
|
476
|
+
if (!found && original !== undefined && mayRestore)
|
|
477
|
+
await move(original.current);
|
|
478
|
+
await setChatPopoverView(page, initial.view);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/** The optional Work fast-mode checkbox is independent of model and effort. */
|
|
482
|
+
export async function selectChatPopoverSpeed(page, labels) {
|
|
483
|
+
const initial = (await readChatPopover(page)).snapshot;
|
|
484
|
+
if (initial === undefined)
|
|
485
|
+
return undefined;
|
|
486
|
+
try {
|
|
487
|
+
const simple = await setChatPopoverView(page, "simple");
|
|
488
|
+
if (simple?.speed === undefined || simple.speedIndex === undefined)
|
|
489
|
+
return undefined;
|
|
490
|
+
const desired = ["Standard", "Fast"].filter(value => labels.some(label => normalizeForLabelMatch(label) === normalizeForLabelMatch(value)));
|
|
491
|
+
if (desired.length !== 1)
|
|
492
|
+
return undefined;
|
|
493
|
+
if (simple.speed === desired[0])
|
|
494
|
+
return simple.speed;
|
|
495
|
+
const candidates = observedRoot(page, simple)?.locator?.('[role="menuitemcheckbox"][data-fast-mode-enabled]');
|
|
496
|
+
const locator = candidates?.nth?.(simple.speedIndex) ?? (simple.speedIndex === 0 ? candidates : undefined);
|
|
497
|
+
if (locator?.click === undefined || locator.evaluate === undefined || await locator.count?.() !== 1)
|
|
498
|
+
return undefined;
|
|
499
|
+
const state = await locator.evaluate(element => {
|
|
500
|
+
let current = element;
|
|
501
|
+
let owned = false, simple = false, open = false;
|
|
502
|
+
for (let depth = 0; current !== null && depth < 64; depth += 1) {
|
|
503
|
+
if (current.nodeType !== 1)
|
|
504
|
+
break;
|
|
505
|
+
const node = current;
|
|
506
|
+
if (node.hidden || node.hasAttribute("hidden") || node.hasAttribute("inert") || node.getAttribute("aria-hidden") === "true"
|
|
507
|
+
|| node.getAttribute("aria-disabled") === "true" || node.getAttribute("data-active") === "false" || node.getAttribute("data-locked") === "true")
|
|
508
|
+
return undefined;
|
|
509
|
+
const style = window.getComputedStyle(node);
|
|
510
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0" || style.pointerEvents === "none")
|
|
511
|
+
return undefined;
|
|
512
|
+
if (node.getAttribute("role") === "menu" && node.getAttribute("data-state") === "open")
|
|
513
|
+
open = true;
|
|
514
|
+
if (node.getAttribute("data-testid") === "composer-intelligence-picker-content")
|
|
515
|
+
owned = true;
|
|
516
|
+
if (node.getAttribute("data-view") === "simple" && node.getAttribute("data-model-selection-view") === "true")
|
|
517
|
+
simple = true;
|
|
518
|
+
current = current.parentNode;
|
|
519
|
+
}
|
|
520
|
+
const rect = element.getBoundingClientRect();
|
|
521
|
+
if (current?.nodeType === 1 || !owned || !simple || !open || rect.width <= 0 || rect.height <= 0
|
|
522
|
+
|| element.getAttribute("role") !== "menuitemcheckbox" || element.getAttribute("data-visible") !== "true")
|
|
523
|
+
return undefined;
|
|
524
|
+
const checked = element.getAttribute("aria-checked");
|
|
525
|
+
return element.getAttribute("data-fast-mode-enabled") === checked ? checked : undefined;
|
|
526
|
+
}).catch(() => undefined);
|
|
527
|
+
if (state !== String(simple.speed === "Fast"))
|
|
528
|
+
return undefined;
|
|
529
|
+
await locator.click();
|
|
530
|
+
await waitForPopoverTransition(page, 150);
|
|
531
|
+
const after = (await readChatPopover(page)).snapshot;
|
|
532
|
+
return after !== undefined && after.speed === desired[0] ? after.speed : undefined;
|
|
533
|
+
}
|
|
534
|
+
finally {
|
|
535
|
+
await setChatPopoverView(page, initial.view);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type MenuItem } from "../dom/menus.js";
|
|
2
2
|
import type { ApplyConfigurationArgs, ApplyConfigurationData, ChatGPTExperience, CommandResult, ConfigurationAxis, ConfigurationInspectionData, ConfigurationSelection, InspectConfigurationArgs, RuntimeEnv, SurfaceSelectorProfile } from "../types.js";
|
|
3
|
+
import { type ChatPopoverSnapshot } from "./chat-popover.js";
|
|
3
4
|
export type ConfigurationPanelSnapshot = {
|
|
4
5
|
openerLabel?: string;
|
|
6
|
+
/** Visible selected value; the accessible opener name may only name the axis. */
|
|
7
|
+
openerValue?: string;
|
|
5
8
|
axisRows: Array<{
|
|
6
9
|
axis: ConfigurationAxis;
|
|
7
10
|
label: string;
|
|
@@ -10,6 +13,8 @@ export type ConfigurationPanelSnapshot = {
|
|
|
10
13
|
advancedVisible: boolean;
|
|
11
14
|
};
|
|
12
15
|
export declare function inspectConfiguration(env: RuntimeEnv, args?: InspectConfigurationArgs): Promise<CommandResult<ConfigurationInspectionData>>;
|
|
16
|
+
/** Shared owned popover, with surface-specific model semantics and observed axes only. */
|
|
17
|
+
export declare function configurationInspectionFromPopover(experience: "chat" | "work", evidence: ConfigurationInspectionData["evidence"], popover: ChatPopoverSnapshot | undefined): ConfigurationInspectionData;
|
|
13
18
|
export declare function applyConfiguration(env: RuntimeEnv, args: ApplyConfigurationArgs): Promise<CommandResult<ApplyConfigurationData>>;
|
|
14
19
|
export declare function configurationInspectionFromSurface(experience: ChatGPTExperience, detectedProfile: SurfaceSelectorProfile, evidence: ConfigurationInspectionData["evidence"], panel: ConfigurationPanelSnapshot, menuItems: MenuItem[]): ConfigurationInspectionData;
|
|
15
20
|
export declare function configurationMatchesSelection(inspection: ConfigurationInspectionData, desired: ConfigurationSelection): boolean;
|