codex-chatgpt-control 0.2.0-alpha.1 → 0.5.0-alpha.1
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 +15 -0
- package/README.md +42 -2
- package/contracts/v1/fixtures/backend-capabilities.json +11 -0
- package/contracts/v1/fixtures/command-descriptors.json +289 -7
- package/contracts/v1/fixtures/describe-runner-run.json +5 -2
- package/contracts/v1/fixtures/doctor-scenario-preflight.json +45 -2
- package/contracts/v1/fixtures/help-root.json +1 -1
- package/contracts/v1/fixtures/output-json-parse-success.json +9 -0
- package/contracts/v1/fixtures/reports-create-redacted.json +1 -1
- package/contracts/v1/fixtures/run-timeout-partial.json +15 -4
- package/contracts/v1/fixtures/stream-in-progress.ndjson +3 -0
- package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
- package/contracts/v1/fixtures/surface-chat-legacy.json +35 -0
- package/contracts/v1/fixtures/surface-chat-simplified.json +37 -0
- package/contracts/v1/fixtures/surface-sidebar-false-positive.json +29 -0
- package/contracts/v1/fixtures/surface-work-advanced.json +45 -0
- package/contracts/v1/fixtures/surface-work-basic.json +38 -0
- package/contracts/v1/fixtures/workflow-ask-success.json +8 -0
- package/contracts/v1/manifest.json +32 -1
- package/contracts/v1/parity-suite.json +121 -1
- package/contracts/v1/schemas/backend-request.schema.json +15 -0
- package/contracts/v1/schemas/manifest.schema.json +6 -3
- package/contracts/v1/schemas/surface-profile.schema.json +166 -0
- package/dist/codex-chatgpt-control-backend.mjs +3198 -340
- package/dist/codex-chatgpt-control.bundle.mjs +4454 -1555
- package/dist/src/backend/client.d.ts +26 -1
- package/dist/src/backend/client.js +23 -1
- package/dist/src/backend/protocol.d.ts +1 -1
- package/dist/src/backend/protocol.js +11 -0
- package/dist/src/backend/session.js +22 -0
- package/dist/src/browser/attach.d.ts +1 -0
- package/dist/src/browser/attach.js +3 -3
- package/dist/src/browser/clipboard.d.ts +11 -0
- package/dist/src/browser/clipboard.js +34 -7
- package/dist/src/browser/page-state.js +17 -4
- package/dist/src/client.d.ts +32 -1
- package/dist/src/client.js +72 -16
- package/dist/src/commands/artifacts.js +1 -7
- package/dist/src/commands/configuration.d.ts +15 -0
- package/dist/src/commands/configuration.js +674 -0
- package/dist/src/commands/context.js +3 -1
- package/dist/src/commands/conversation.d.ts +15 -0
- package/dist/src/commands/conversation.js +44 -0
- package/dist/src/commands/deadline.d.ts +8 -0
- package/dist/src/commands/deadline.js +14 -0
- package/dist/src/commands/doctor.js +47 -3
- package/dist/src/commands/experience.d.ts +12 -0
- package/dist/src/commands/experience.js +288 -0
- package/dist/src/commands/files.js +61 -13
- package/dist/src/commands/messages.d.ts +2 -1
- package/dist/src/commands/messages.js +349 -87
- package/dist/src/commands/modes.d.ts +4 -1
- package/dist/src/commands/modes.js +243 -44
- package/dist/src/commands/probes.d.ts +15 -0
- package/dist/src/commands/probes.js +64 -0
- package/dist/src/commands/registry.js +102 -8
- package/dist/src/commands/reports.js +1 -1
- package/dist/src/commands/response-actions.js +1 -7
- package/dist/src/commands/sequence.js +24 -1
- package/dist/src/commands/session.d.ts +2 -0
- package/dist/src/commands/session.js +50 -1
- package/dist/src/commands/threads.js +3 -31
- package/dist/src/commands/work.d.ts +6 -0
- package/dist/src/commands/work.js +338 -0
- package/dist/src/dom/generation-state.d.ts +7 -0
- package/dist/src/dom/generation-state.js +7 -1
- package/dist/src/dom/label-match.d.ts +3 -0
- package/dist/src/dom/label-match.js +25 -0
- package/dist/src/dom/locale/am.d.ts +8 -1
- package/dist/src/dom/locale/am.js +8 -1
- package/dist/src/dom/locale/ar.d.ts +9 -1
- package/dist/src/dom/locale/ar.js +9 -1
- package/dist/src/dom/locale/bg.d.ts +9 -1
- package/dist/src/dom/locale/bg.js +9 -1
- package/dist/src/dom/locale/bn.d.ts +9 -1
- package/dist/src/dom/locale/bn.js +9 -1
- package/dist/src/dom/locale/bs.d.ts +8 -1
- package/dist/src/dom/locale/bs.js +8 -1
- package/dist/src/dom/locale/ca.d.ts +8 -1
- package/dist/src/dom/locale/ca.js +8 -1
- package/dist/src/dom/locale/cs.d.ts +8 -1
- package/dist/src/dom/locale/cs.js +8 -1
- package/dist/src/dom/locale/da.d.ts +7 -1
- package/dist/src/dom/locale/da.js +7 -1
- package/dist/src/dom/locale/de.d.ts +10 -3
- package/dist/src/dom/locale/de.js +10 -3
- package/dist/src/dom/locale/el.d.ts +8 -1
- package/dist/src/dom/locale/el.js +8 -1
- package/dist/src/dom/locale/en.d.ts +40 -1
- package/dist/src/dom/locale/en.js +42 -1
- package/dist/src/dom/locale/es-419.d.ts +8 -1
- package/dist/src/dom/locale/es-419.js +8 -1
- package/dist/src/dom/locale/es-ES.d.ts +8 -1
- package/dist/src/dom/locale/es-ES.js +8 -1
- package/dist/src/dom/locale/et.d.ts +8 -1
- package/dist/src/dom/locale/et.js +8 -1
- package/dist/src/dom/locale/fa.d.ts +9 -1
- package/dist/src/dom/locale/fa.js +9 -1
- package/dist/src/dom/locale/fi.d.ts +8 -1
- package/dist/src/dom/locale/fi.js +8 -1
- package/dist/src/dom/locale/fr-CA.d.ts +8 -1
- package/dist/src/dom/locale/fr-CA.js +8 -1
- package/dist/src/dom/locale/fr-FR.d.ts +8 -3
- package/dist/src/dom/locale/fr-FR.js +8 -3
- package/dist/src/dom/locale/gu.d.ts +8 -1
- package/dist/src/dom/locale/gu.js +8 -1
- package/dist/src/dom/locale/hi.d.ts +8 -1
- package/dist/src/dom/locale/hi.js +8 -1
- package/dist/src/dom/locale/hr.d.ts +7 -1
- package/dist/src/dom/locale/hr.js +7 -1
- package/dist/src/dom/locale/hu.d.ts +8 -1
- package/dist/src/dom/locale/hu.js +8 -1
- package/dist/src/dom/locale/hy.d.ts +9 -1
- package/dist/src/dom/locale/hy.js +9 -1
- package/dist/src/dom/locale/id.d.ts +8 -1
- package/dist/src/dom/locale/id.js +8 -1
- package/dist/src/dom/locale/index.d.ts +20 -1
- package/dist/src/dom/locale/index.js +102 -0
- package/dist/src/dom/locale/is.d.ts +8 -1
- package/dist/src/dom/locale/is.js +8 -1
- package/dist/src/dom/locale/it.d.ts +8 -1
- package/dist/src/dom/locale/it.js +8 -1
- package/dist/src/dom/locale/ja.d.ts +8 -1
- package/dist/src/dom/locale/ja.js +8 -1
- package/dist/src/dom/locale/ka.d.ts +8 -1
- package/dist/src/dom/locale/ka.js +8 -1
- package/dist/src/dom/locale/kk.d.ts +8 -1
- package/dist/src/dom/locale/kk.js +8 -1
- package/dist/src/dom/locale/kn.d.ts +9 -1
- package/dist/src/dom/locale/kn.js +9 -1
- package/dist/src/dom/locale/ko.d.ts +8 -1
- package/dist/src/dom/locale/ko.js +8 -1
- package/dist/src/dom/locale/lt.d.ts +9 -1
- package/dist/src/dom/locale/lt.js +9 -1
- package/dist/src/dom/locale/lv.d.ts +8 -1
- package/dist/src/dom/locale/lv.js +8 -1
- package/dist/src/dom/locale/mk.d.ts +7 -1
- package/dist/src/dom/locale/mk.js +7 -1
- package/dist/src/dom/locale/ml.d.ts +9 -1
- package/dist/src/dom/locale/ml.js +9 -1
- package/dist/src/dom/locale/mn.d.ts +9 -1
- package/dist/src/dom/locale/mn.js +9 -1
- package/dist/src/dom/locale/mr.d.ts +9 -1
- package/dist/src/dom/locale/mr.js +9 -1
- package/dist/src/dom/locale/ms.d.ts +8 -1
- package/dist/src/dom/locale/ms.js +8 -1
- package/dist/src/dom/locale/my.d.ts +8 -1
- package/dist/src/dom/locale/my.js +8 -1
- package/dist/src/dom/locale/nb.d.ts +8 -1
- package/dist/src/dom/locale/nb.js +8 -1
- package/dist/src/dom/locale/nl.d.ts +8 -1
- package/dist/src/dom/locale/nl.js +8 -1
- package/dist/src/dom/locale/pa.d.ts +9 -1
- package/dist/src/dom/locale/pa.js +9 -1
- package/dist/src/dom/locale/pl.d.ts +8 -1
- package/dist/src/dom/locale/pl.js +8 -1
- package/dist/src/dom/locale/pt-BR.d.ts +8 -1
- package/dist/src/dom/locale/pt-BR.js +8 -1
- package/dist/src/dom/locale/pt-PT.d.ts +8 -1
- package/dist/src/dom/locale/pt-PT.js +8 -1
- package/dist/src/dom/locale/ro.d.ts +7 -1
- package/dist/src/dom/locale/ro.js +7 -1
- package/dist/src/dom/locale/ru.d.ts +5 -1
- package/dist/src/dom/locale/ru.js +5 -1
- package/dist/src/dom/locale/sk.d.ts +8 -1
- package/dist/src/dom/locale/sk.js +8 -1
- package/dist/src/dom/locale/sl.d.ts +8 -1
- package/dist/src/dom/locale/sl.js +8 -1
- package/dist/src/dom/locale/so.d.ts +8 -1
- package/dist/src/dom/locale/so.js +8 -1
- package/dist/src/dom/locale/sq.d.ts +8 -1
- package/dist/src/dom/locale/sq.js +8 -1
- package/dist/src/dom/locale/sr.d.ts +5 -1
- package/dist/src/dom/locale/sr.js +5 -1
- package/dist/src/dom/locale/sv.d.ts +8 -1
- package/dist/src/dom/locale/sv.js +8 -1
- package/dist/src/dom/locale/sw.d.ts +8 -1
- package/dist/src/dom/locale/sw.js +8 -1
- package/dist/src/dom/locale/ta.d.ts +9 -1
- package/dist/src/dom/locale/ta.js +9 -1
- package/dist/src/dom/locale/te.d.ts +9 -1
- package/dist/src/dom/locale/te.js +9 -1
- package/dist/src/dom/locale/th.d.ts +8 -1
- package/dist/src/dom/locale/th.js +8 -1
- package/dist/src/dom/locale/tl.d.ts +4 -5
- package/dist/src/dom/locale/tl.js +4 -5
- package/dist/src/dom/locale/tr.d.ts +8 -1
- package/dist/src/dom/locale/tr.js +8 -1
- package/dist/src/dom/locale/types.d.ts +25 -1
- package/dist/src/dom/locale/uk.d.ts +8 -1
- package/dist/src/dom/locale/uk.js +8 -1
- package/dist/src/dom/locale/ur.d.ts +8 -1
- package/dist/src/dom/locale/ur.js +8 -1
- package/dist/src/dom/locale/vi.d.ts +8 -1
- package/dist/src/dom/locale/vi.js +8 -1
- package/dist/src/dom/locale/zh-HK.d.ts +9 -1
- package/dist/src/dom/locale/zh-HK.js +10 -2
- package/dist/src/dom/locale/zh-Hans.d.ts +9 -1
- package/dist/src/dom/locale/zh-Hans.js +9 -1
- package/dist/src/dom/locale/zh-TW.d.ts +9 -1
- package/dist/src/dom/locale/zh-TW.js +9 -1
- package/dist/src/dom/menus.js +13 -2
- package/dist/src/dom/selectors.js +6 -1
- package/dist/src/dom/wait-snapshot.d.ts +43 -0
- package/dist/src/dom/wait-snapshot.js +127 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +16 -1
- package/dist/src/runner/result.js +129 -4
- package/dist/src/runner/stream.d.ts +1 -1
- package/dist/src/runner/stream.js +6 -0
- package/dist/src/runner/types.d.ts +35 -1
- package/dist/src/safety/risk.d.ts +11 -0
- package/dist/src/safety/risk.js +11 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +6 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.js +147 -15
- package/dist/src/scripts/capture-intelligence-locales.d.ts +3 -0
- package/dist/src/scripts/capture-intelligence-locales.js +372 -3
- package/dist/src/scripts/capture-surface-profile.d.ts +23 -0
- package/dist/src/scripts/capture-surface-profile.js +304 -0
- package/dist/src/scripts/live-smoke/scenarios.js +34 -1
- package/dist/src/scripts/live-smoke.js +1 -0
- package/dist/src/types.d.ts +273 -2
- package/package.json +4 -3
- package/references/2026-07-16-chat-work-surfaces.md +87 -0
- package/references/agents-runner.md +13 -2
- package/references/backend-protocol.md +19 -7
- package/references/language-coverage.md +40 -3
- package/references/localization.md +48 -10
- package/references/python-parity.md +16 -4
- package/references/responses-adapter.md +12 -1
- package/references/troubleshooting.md +49 -6
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { enumerateVisibleMenuItems } from "../dom/menus.js";
|
|
2
|
+
import { localeLabels } from "../dom/locale-labels.js";
|
|
3
|
+
import { normalizeForLabelMatch, visibleLabelMatches } from "../dom/label-match.js";
|
|
4
|
+
import { resultError, resultOk } from "../errors.js";
|
|
5
|
+
import { contextFromPage } from "./context.js";
|
|
6
|
+
import { detectExperience, openExperience } from "./experience.js";
|
|
7
|
+
import { setMode } from "./modes.js";
|
|
8
|
+
import { ensurePage } from "./session.js";
|
|
9
|
+
const WORK_AXES = ["model", "effort", "speed"];
|
|
10
|
+
const CONFIGURATION_AXIS_ORDER = [
|
|
11
|
+
"model",
|
|
12
|
+
"intelligence",
|
|
13
|
+
"effort",
|
|
14
|
+
"speed",
|
|
15
|
+
"modelVersion",
|
|
16
|
+
];
|
|
17
|
+
export async function inspectConfiguration(env, args = {}) {
|
|
18
|
+
const boot = await ensurePage(env);
|
|
19
|
+
if (!boot.ok) {
|
|
20
|
+
return boot;
|
|
21
|
+
}
|
|
22
|
+
const page = env.page;
|
|
23
|
+
try {
|
|
24
|
+
const detected = await detectExperience(env, args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs });
|
|
25
|
+
if (!detected.ok || detected.data === undefined) {
|
|
26
|
+
return forwardFailure(detected);
|
|
27
|
+
}
|
|
28
|
+
if (args.experience !== undefined && detected.data.experience !== args.experience) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
status: "unsupported",
|
|
32
|
+
warnings: [],
|
|
33
|
+
blocker: {
|
|
34
|
+
kind: "selector_drift",
|
|
35
|
+
code: "experience_mismatch",
|
|
36
|
+
fieldPath: "experience",
|
|
37
|
+
message: `Configuration inspection expected ${args.experience}, but the visible composer is ${detected.data.experience}. Call experience.open first or omit the expected experience.`,
|
|
38
|
+
resumable: true
|
|
39
|
+
},
|
|
40
|
+
context: await contextFromPage(page, {
|
|
41
|
+
experience: detected.data.experience,
|
|
42
|
+
selectorProfile: detected.data.selectorProfile
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const experience = detected.data.experience;
|
|
47
|
+
const rootOpened = experience !== "unknown" && await openConfigurationRoot(page, experience);
|
|
48
|
+
if (rootOpened) {
|
|
49
|
+
await page.waitForTimeout?.(150);
|
|
50
|
+
}
|
|
51
|
+
const panel = await readConfigurationPanel(page);
|
|
52
|
+
const rootItems = rootOpened ? await enumerateVisibleMenuItems(page) : [];
|
|
53
|
+
const data = configurationInspectionFromSurface(experience, detected.data.selectorProfile, detected.data.evidence, panel, rootItems);
|
|
54
|
+
if (args.includeOptions !== false && experience === "work" && panel.axisRows.length > 0) {
|
|
55
|
+
for (const axis of WORK_AXES) {
|
|
56
|
+
if (!data.availableAxes.includes(axis))
|
|
57
|
+
continue;
|
|
58
|
+
const options = await inspectWorkAxisOptions(page, axis);
|
|
59
|
+
if (options.length > 0) {
|
|
60
|
+
data.options[axis] = options;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
await closeConfigurationMenus(page);
|
|
64
|
+
}
|
|
65
|
+
const warnings = [];
|
|
66
|
+
if (!rootOpened) {
|
|
67
|
+
warnings.push("No scoped configuration opener was available; inspection is limited to controls already visible in the composer.");
|
|
68
|
+
}
|
|
69
|
+
if (!data.verified) {
|
|
70
|
+
warnings.push("The visible configuration could not be verified from a recognized Chat or Work selector profile.");
|
|
71
|
+
}
|
|
72
|
+
return resultOk(data, await contextFromPage(page, {
|
|
73
|
+
experience: data.experience,
|
|
74
|
+
selectorProfile: data.selectorProfile
|
|
75
|
+
}), warnings);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function applyConfiguration(env, args) {
|
|
82
|
+
const boot = await ensurePage(env);
|
|
83
|
+
if (!boot.ok) {
|
|
84
|
+
return boot;
|
|
85
|
+
}
|
|
86
|
+
const page = env.page;
|
|
87
|
+
const strict = args.strict ?? true;
|
|
88
|
+
try {
|
|
89
|
+
const desired = normalizeDesiredSelection(args.desired);
|
|
90
|
+
if (selectionEntries(desired).length === 0) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
status: "unsupported",
|
|
94
|
+
warnings: [],
|
|
95
|
+
blocker: {
|
|
96
|
+
kind: "selector_drift",
|
|
97
|
+
code: "configuration_empty",
|
|
98
|
+
fieldPath: "desired",
|
|
99
|
+
message: "configuration.apply requires at least one desired model, intelligence, effort, speed, or modelVersion value.",
|
|
100
|
+
resumable: false
|
|
101
|
+
},
|
|
102
|
+
context: await contextFromPage(page)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (args.experience !== undefined) {
|
|
106
|
+
const opened = await openExperience(env, {
|
|
107
|
+
experience: args.experience,
|
|
108
|
+
...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs })
|
|
109
|
+
});
|
|
110
|
+
if (!opened.ok) {
|
|
111
|
+
return forwardFailure(opened);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const beforeResult = await inspectConfiguration(env, {
|
|
115
|
+
...(args.experience === undefined ? {} : { experience: args.experience }),
|
|
116
|
+
includeOptions: true,
|
|
117
|
+
...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs })
|
|
118
|
+
});
|
|
119
|
+
if (!beforeResult.ok || beforeResult.data === undefined) {
|
|
120
|
+
return forwardFailure(beforeResult);
|
|
121
|
+
}
|
|
122
|
+
const before = beforeResult.data;
|
|
123
|
+
if (before.experience === "unknown") {
|
|
124
|
+
return configurationFailure(page, before, desired, [], "The visible surface is not recognizable as Chat or Work.", "experience_unknown");
|
|
125
|
+
}
|
|
126
|
+
const selected = [];
|
|
127
|
+
for (const [axis, requested] of selectionEntries(desired)) {
|
|
128
|
+
const active = activeConfigurationValue(before, axis);
|
|
129
|
+
if (active !== undefined && configurationValueMatches(active, requested)) {
|
|
130
|
+
selected.push({ axis, requested, selected: active });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const selection = before.experience === "work"
|
|
134
|
+
? await selectWorkAxis(page, axis, requested)
|
|
135
|
+
: await selectChatAxis(env, axis, requested, args.timeoutMs);
|
|
136
|
+
if (selection === undefined) {
|
|
137
|
+
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));
|
|
138
|
+
}
|
|
139
|
+
selected.push({ axis, requested, selected: selection });
|
|
140
|
+
}
|
|
141
|
+
const afterResult = await inspectConfiguration(env, {
|
|
142
|
+
...(args.experience === undefined ? {} : { experience: args.experience }),
|
|
143
|
+
includeOptions: false,
|
|
144
|
+
...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs })
|
|
145
|
+
});
|
|
146
|
+
if (!afterResult.ok || afterResult.data === undefined) {
|
|
147
|
+
return forwardFailure(afterResult);
|
|
148
|
+
}
|
|
149
|
+
const after = afterResult.data;
|
|
150
|
+
const verified = configurationMatchesSelection(after, desired);
|
|
151
|
+
const data = { requested: desired, selected, before, after, verified };
|
|
152
|
+
if (!verified && strict) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
status: "blocked",
|
|
156
|
+
data,
|
|
157
|
+
warnings: [],
|
|
158
|
+
blocker: {
|
|
159
|
+
kind: "selector_drift",
|
|
160
|
+
code: "configuration_postcondition_unverified",
|
|
161
|
+
fieldPath: "desired",
|
|
162
|
+
message: `ChatGPT accepted configuration clicks, but the visible ${after.experience} controls do not verify every requested value.`,
|
|
163
|
+
candidates: Object.entries(after.active).map(([axis, label]) => ({ label: `${axis}: ${label}` })),
|
|
164
|
+
resumable: true
|
|
165
|
+
},
|
|
166
|
+
context: await contextFromPage(page, {
|
|
167
|
+
experience: after.experience,
|
|
168
|
+
selectorProfile: after.selectorProfile
|
|
169
|
+
})
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const warnings = verified
|
|
173
|
+
? []
|
|
174
|
+
: ["Configuration clicks completed, but strict verification was disabled and the visible postcondition remains unverified."];
|
|
175
|
+
return resultOk(data, await contextFromPage(page, {
|
|
176
|
+
experience: after.experience,
|
|
177
|
+
selectorProfile: after.selectorProfile
|
|
178
|
+
}), warnings);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export function configurationInspectionFromSurface(experience, detectedProfile, evidence, panel, menuItems) {
|
|
185
|
+
const active = {};
|
|
186
|
+
const options = {};
|
|
187
|
+
const availableAxes = [];
|
|
188
|
+
let selectorProfile = detectedProfile;
|
|
189
|
+
if (experience === "work") {
|
|
190
|
+
for (const row of panel.axisRows) {
|
|
191
|
+
if (!availableAxes.includes(row.axis))
|
|
192
|
+
availableAxes.push(row.axis);
|
|
193
|
+
if (row.value !== undefined && row.value.length > 0)
|
|
194
|
+
active[row.axis] = row.value;
|
|
195
|
+
}
|
|
196
|
+
selectorProfile = panel.advancedVisible ? "work_advanced_v1" : "work_basic_v1";
|
|
197
|
+
}
|
|
198
|
+
else if (experience === "chat") {
|
|
199
|
+
const simplified = chatMenuLooksSimplified(menuItems);
|
|
200
|
+
selectorProfile = simplified ? "chat_simplified_v1" : detectedProfile;
|
|
201
|
+
const axis = simplified ? "intelligence" : "effort";
|
|
202
|
+
if (menuItems.length > 0 || panel.openerLabel !== undefined) {
|
|
203
|
+
availableAxes.push(axis);
|
|
204
|
+
}
|
|
205
|
+
if (panel.openerLabel !== undefined) {
|
|
206
|
+
active[axis] = panel.openerLabel;
|
|
207
|
+
}
|
|
208
|
+
const chatOptions = menuItems
|
|
209
|
+
.filter(item => !isConfigurationAxisRow(item.label))
|
|
210
|
+
.map(menuItemToOption);
|
|
211
|
+
if (chatOptions.length > 0) {
|
|
212
|
+
options[axis] = chatOptions;
|
|
213
|
+
}
|
|
214
|
+
const modelRows = menuItems.filter(item => /^gpt[\s-]/i.test(item.label) || item.hasPopup === true);
|
|
215
|
+
if (modelRows.length > 0) {
|
|
216
|
+
availableAxes.push("modelVersion");
|
|
217
|
+
options.modelVersion = modelRows.map(menuItemToOption);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
experience,
|
|
222
|
+
selectorProfile,
|
|
223
|
+
availableAxes,
|
|
224
|
+
active,
|
|
225
|
+
options,
|
|
226
|
+
verified: experience !== "unknown" && (availableAxes.length > 0 || Object.keys(active).length > 0),
|
|
227
|
+
evidence
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
async function inspectWorkAxisOptions(page, axis) {
|
|
231
|
+
await ensureWorkConfigurationRoot(page);
|
|
232
|
+
if (!await clickWorkAxisRow(page, axis)) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
await page.waitForTimeout?.(120);
|
|
236
|
+
const items = await enumerateVisibleMenuItems(page);
|
|
237
|
+
const options = items
|
|
238
|
+
.filter(item => !isConfigurationAxisRow(item.label))
|
|
239
|
+
.map(menuItemToOption);
|
|
240
|
+
await page.keyboard?.press?.("Escape");
|
|
241
|
+
await page.waitForTimeout?.(80);
|
|
242
|
+
return dedupeOptions(options);
|
|
243
|
+
}
|
|
244
|
+
async function selectWorkAxis(page, axis, requested) {
|
|
245
|
+
if (!WORK_AXES.includes(axis)) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
await ensureWorkConfigurationRoot(page);
|
|
249
|
+
if (!await clickWorkAxisRow(page, axis)) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
await page.waitForTimeout?.(120);
|
|
253
|
+
const candidates = await enumerateVisibleMenuItems(page);
|
|
254
|
+
const match = findConfigurationOption(candidates, requested);
|
|
255
|
+
if (match === undefined || !await clickVisibleMenuItem(page, match)) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
await page.waitForTimeout?.(150);
|
|
259
|
+
return match.label;
|
|
260
|
+
}
|
|
261
|
+
async function selectChatAxis(env, axis, requested, timeoutMs) {
|
|
262
|
+
const legacyArgs = axis === "modelVersion"
|
|
263
|
+
? { modelVersion: requested }
|
|
264
|
+
: axis === "intelligence"
|
|
265
|
+
? { intelligence: requested }
|
|
266
|
+
: axis === "effort"
|
|
267
|
+
? { effort: requested }
|
|
268
|
+
: axis === "model"
|
|
269
|
+
? { model: requested }
|
|
270
|
+
: undefined;
|
|
271
|
+
if (legacyArgs === undefined) {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
const result = await setMode(env, {
|
|
275
|
+
...legacyArgs,
|
|
276
|
+
...(timeoutMs === undefined ? {} : { timeoutMs })
|
|
277
|
+
});
|
|
278
|
+
return result.ok ? result.data?.selected.at(-1) : undefined;
|
|
279
|
+
}
|
|
280
|
+
async function openConfigurationRoot(page, experience) {
|
|
281
|
+
const existing = await readConfigurationPanel(page);
|
|
282
|
+
if (existing.axisRows.length > 0) {
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
const existingItems = await enumerateVisibleMenuItems(page).catch(() => []);
|
|
286
|
+
if (configurationMenuLooksRecognized(existingItems, experience, existing.openerLabel)) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
if (typeof page.evaluate === "function") {
|
|
290
|
+
const clicked = await page.evaluate((surface) => {
|
|
291
|
+
const normalize = (value) => value.replace(/\s+/g, " ").trim();
|
|
292
|
+
const visible = (element) => {
|
|
293
|
+
const html = element;
|
|
294
|
+
const rect = html.getBoundingClientRect?.();
|
|
295
|
+
const style = typeof window !== "undefined" ? window.getComputedStyle?.(html) : undefined;
|
|
296
|
+
return (rect === undefined || (rect.width > 0 && rect.height > 0))
|
|
297
|
+
&& style?.display !== "none"
|
|
298
|
+
&& style?.visibility !== "hidden"
|
|
299
|
+
&& style?.opacity !== "0";
|
|
300
|
+
};
|
|
301
|
+
const forms = Array.from(document.querySelectorAll("main form, main [data-testid*='composer' i]"));
|
|
302
|
+
const controls = forms.flatMap(form => Array.from(form.querySelectorAll("button, [role='button']")))
|
|
303
|
+
.filter(visible);
|
|
304
|
+
const matches = controls.filter(control => {
|
|
305
|
+
const html = control;
|
|
306
|
+
const label = normalize(control.getAttribute("aria-label") ?? html.innerText ?? control.textContent ?? "");
|
|
307
|
+
const testId = control.getAttribute("data-testid") ?? "";
|
|
308
|
+
if (/send|voice|microphone|attach|upload|add files|plus/i.test(`${label} ${testId}`))
|
|
309
|
+
return false;
|
|
310
|
+
if (/model-switcher|model-selector|mode-selector/i.test(testId))
|
|
311
|
+
return true;
|
|
312
|
+
return surface === "work"
|
|
313
|
+
? /\b(?:gpt|sol|luna|terra|light|medium|high|max|ultra|standard|fast)\b/i.test(label)
|
|
314
|
+
: /\b(?:instant|medium|high|extra high|pro|thinking|extended|gpt)\b/i.test(label);
|
|
315
|
+
});
|
|
316
|
+
if (matches.length !== 1)
|
|
317
|
+
return false;
|
|
318
|
+
matches[0].click();
|
|
319
|
+
return true;
|
|
320
|
+
}, experience).catch(() => false);
|
|
321
|
+
if (clicked)
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
const labels = experience === "work"
|
|
325
|
+
? [
|
|
326
|
+
...localeLabels.configurationOptions.light,
|
|
327
|
+
...localeLabels.configurationOptions.medium,
|
|
328
|
+
...localeLabels.configurationOptions.high,
|
|
329
|
+
...localeLabels.configurationOptions.standard,
|
|
330
|
+
]
|
|
331
|
+
: [
|
|
332
|
+
...localeLabels.configurationOptions.instant,
|
|
333
|
+
...localeLabels.configurationOptions.medium,
|
|
334
|
+
...localeLabels.configurationOptions.high,
|
|
335
|
+
...localeLabels.configurationOptions.extraHigh,
|
|
336
|
+
...localeLabels.configurationOptions.pro,
|
|
337
|
+
...localeLabels.modeOptions.thinking,
|
|
338
|
+
];
|
|
339
|
+
for (const label of labels) {
|
|
340
|
+
if (await clickIfUnique(page.getByRole?.("button", { name: label, exact: true }))) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
function configurationMenuLooksRecognized(items, experience, openerLabel) {
|
|
347
|
+
if (items.some(item => /(?:model|mode|effort|speed)-(?:switcher|selector)|model-switcher/i.test(item.testId ?? ""))) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
if (openerLabel === undefined || items.length === 0) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
const semanticLabels = experience === "work"
|
|
354
|
+
? [
|
|
355
|
+
...localeLabels.configurationOptions.light,
|
|
356
|
+
...localeLabels.configurationOptions.medium,
|
|
357
|
+
...localeLabels.configurationOptions.high,
|
|
358
|
+
...localeLabels.configurationOptions.max,
|
|
359
|
+
...localeLabels.configurationOptions.ultra,
|
|
360
|
+
...localeLabels.configurationOptions.standard,
|
|
361
|
+
...localeLabels.configurationOptions.fast,
|
|
362
|
+
]
|
|
363
|
+
: [
|
|
364
|
+
...localeLabels.configurationOptions.instant,
|
|
365
|
+
...localeLabels.configurationOptions.medium,
|
|
366
|
+
...localeLabels.configurationOptions.high,
|
|
367
|
+
...localeLabels.configurationOptions.extraHigh,
|
|
368
|
+
...localeLabels.configurationOptions.pro,
|
|
369
|
+
...localeLabels.modeOptions.thinking,
|
|
370
|
+
...localeLabels.modeOptions.extended,
|
|
371
|
+
];
|
|
372
|
+
const matched = new Set(items
|
|
373
|
+
.filter(item => semanticLabels.some(label => visibleLabelMatches(item.label, label)))
|
|
374
|
+
.map(item => normalizeConfigurationId(item.label)));
|
|
375
|
+
return matched.size >= 2;
|
|
376
|
+
}
|
|
377
|
+
async function ensureWorkConfigurationRoot(page) {
|
|
378
|
+
const panel = await readConfigurationPanel(page);
|
|
379
|
+
if (panel.axisRows.length > 0)
|
|
380
|
+
return true;
|
|
381
|
+
await page.keyboard?.press?.("Escape");
|
|
382
|
+
await page.waitForTimeout?.(50);
|
|
383
|
+
return openConfigurationRoot(page, "work");
|
|
384
|
+
}
|
|
385
|
+
async function readConfigurationPanel(page) {
|
|
386
|
+
if (typeof page.evaluate !== "function") {
|
|
387
|
+
return { axisRows: [], advancedVisible: false };
|
|
388
|
+
}
|
|
389
|
+
return page.evaluate((axisLabels) => {
|
|
390
|
+
const normalize = (value) => value.replace(/\s+/g, " ").trim();
|
|
391
|
+
const normalizedAxes = Object.fromEntries(Object.entries(axisLabels).map(([axis, labels]) => [
|
|
392
|
+
axis,
|
|
393
|
+
labels.map(label => normalize(label).toLocaleLowerCase())
|
|
394
|
+
]));
|
|
395
|
+
const visible = (element) => {
|
|
396
|
+
const html = element;
|
|
397
|
+
const rect = html.getBoundingClientRect?.();
|
|
398
|
+
const style = typeof window !== "undefined" ? window.getComputedStyle?.(html) : undefined;
|
|
399
|
+
return (rect === undefined || (rect.width > 0 && rect.height > 0))
|
|
400
|
+
&& style?.display !== "none"
|
|
401
|
+
&& style?.visibility !== "hidden"
|
|
402
|
+
&& style?.opacity !== "0";
|
|
403
|
+
};
|
|
404
|
+
const overlays = Array.from(document.querySelectorAll("[role='menu'], [role='listbox'], [data-radix-popper-content-wrapper], [data-radix-menu-content]")).filter(visible);
|
|
405
|
+
const roots = overlays.length > 0 ? overlays : Array.from(document.querySelectorAll("main")).filter(visible);
|
|
406
|
+
const rows = roots.flatMap(root => Array.from(root.querySelectorAll("button, [role='button'], [role='menuitem'], [role='menuitemradio'], [role='option']"))).filter(visible);
|
|
407
|
+
const axisRows = [];
|
|
408
|
+
let advancedVisible = false;
|
|
409
|
+
for (const row of rows) {
|
|
410
|
+
const html = row;
|
|
411
|
+
const label = normalize(row.getAttribute("aria-label") ?? html.innerText ?? row.textContent ?? "");
|
|
412
|
+
const normalized = label.toLocaleLowerCase();
|
|
413
|
+
if (normalizedAxes.advanced?.some(candidate => normalized === candidate || normalized.startsWith(`${candidate} `))) {
|
|
414
|
+
advancedVisible = true;
|
|
415
|
+
}
|
|
416
|
+
for (const axis of ["model", "intelligence", "effort", "speed"]) {
|
|
417
|
+
const candidates = normalizedAxes[axis] ?? [];
|
|
418
|
+
const prefix = candidates.find(candidate => normalized === candidate || normalized.startsWith(`${candidate} `));
|
|
419
|
+
if (prefix === undefined)
|
|
420
|
+
continue;
|
|
421
|
+
const value = normalize(label.slice(prefix.length));
|
|
422
|
+
const item = { axis, label };
|
|
423
|
+
if (value.length > 0)
|
|
424
|
+
item.value = value;
|
|
425
|
+
axisRows.push(item);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const forms = Array.from(document.querySelectorAll("main form, main [data-testid*='composer' i]"));
|
|
430
|
+
const openerCandidates = forms.flatMap(form => Array.from(form.querySelectorAll("button, [role='button']")))
|
|
431
|
+
.filter(visible)
|
|
432
|
+
.map(control => {
|
|
433
|
+
const html = control;
|
|
434
|
+
return {
|
|
435
|
+
label: normalize(control.getAttribute("aria-label") ?? html.innerText ?? control.textContent ?? ""),
|
|
436
|
+
testId: control.getAttribute("data-testid") ?? ""
|
|
437
|
+
};
|
|
438
|
+
})
|
|
439
|
+
.filter(item => !/send|voice|microphone|attach|upload|add files|plus/i.test(`${item.label} ${item.testId}`))
|
|
440
|
+
.filter(item => /model-switcher|model-selector|mode-selector/i.test(item.testId)
|
|
441
|
+
|| /\b(?:gpt|sol|luna|terra|instant|medium|high|extra high|pro|thinking|extended|light|standard|fast)\b/i.test(item.label));
|
|
442
|
+
const result = {
|
|
443
|
+
axisRows,
|
|
444
|
+
advancedVisible
|
|
445
|
+
};
|
|
446
|
+
if (openerCandidates.length === 1 && openerCandidates[0]?.label.length) {
|
|
447
|
+
result.openerLabel = openerCandidates[0].label;
|
|
448
|
+
}
|
|
449
|
+
return result;
|
|
450
|
+
}, localeLabels.configurationAxes).catch(() => ({ axisRows: [], advancedVisible: false }));
|
|
451
|
+
}
|
|
452
|
+
async function clickWorkAxisRow(page, axis) {
|
|
453
|
+
const labels = axis === "modelVersion" ? [] : localeLabels.configurationAxes[axis] ?? [];
|
|
454
|
+
for (const label of labels) {
|
|
455
|
+
const pattern = new RegExp(`^${escapeRegExp(label)}(?:\\s|$)`, "i");
|
|
456
|
+
for (const role of ["button", "menuitem"]) {
|
|
457
|
+
if (await clickIfUnique(page.getByRole?.(role, { name: pattern }))) {
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (typeof page.evaluate !== "function")
|
|
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);
|
|
479
|
+
}
|
|
480
|
+
async function clickVisibleMenuItem(page, item) {
|
|
481
|
+
if (item.testId !== undefined && await clickIfUnique(page.locator?.(`[data-testid="${escapeAttributeValue(item.testId)}"]`))) {
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
for (const role of ["menuitemradio", "menuitem", "option"]) {
|
|
485
|
+
if (await clickIfUnique(page.getByRole?.(role, { name: item.label, exact: true }))) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return clickIfUnique(page.getByText?.(item.label, { exact: true }));
|
|
490
|
+
}
|
|
491
|
+
function findConfigurationOption(items, requested) {
|
|
492
|
+
const normalizedRequested = normalizeConfigurationId(requested);
|
|
493
|
+
const exact = items.filter(item => normalizeConfigurationId(item.label) === normalizedRequested);
|
|
494
|
+
if (exact.length === 1)
|
|
495
|
+
return exact[0];
|
|
496
|
+
const semanticLabels = configurationSemanticLabels(requested);
|
|
497
|
+
for (const wanted of semanticLabels) {
|
|
498
|
+
const matches = items.filter(item => normalizeForLabelMatch(item.label) === normalizeForLabelMatch(wanted)
|
|
499
|
+
|| visibleLabelMatches(item.label, wanted));
|
|
500
|
+
if (matches.length === 1)
|
|
501
|
+
return matches[0];
|
|
502
|
+
}
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
function configurationSemanticLabels(requested) {
|
|
506
|
+
const normalized = normalizeConfigurationId(requested);
|
|
507
|
+
for (const labels of Object.values(localeLabels.configurationOptions)) {
|
|
508
|
+
if (labels.some(label => normalizeConfigurationId(label) === normalized)) {
|
|
509
|
+
return labels;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
for (const labels of Object.values(localeLabels.modeOptions)) {
|
|
513
|
+
if (labels.some(label => normalizeConfigurationId(label) === normalized)) {
|
|
514
|
+
return labels;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return [requested];
|
|
518
|
+
}
|
|
519
|
+
export function configurationMatchesSelection(inspection, desired) {
|
|
520
|
+
return selectionEntries(desired).every(([axis, requested]) => {
|
|
521
|
+
const active = activeConfigurationValue(inspection, axis);
|
|
522
|
+
return active !== undefined && configurationValueMatches(active, requested);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
function activeConfigurationValue(inspection, axis) {
|
|
526
|
+
const direct = inspection.active[axis];
|
|
527
|
+
if (direct !== undefined || inspection.experience !== "chat") {
|
|
528
|
+
return direct;
|
|
529
|
+
}
|
|
530
|
+
if (axis === "model" || axis === "intelligence") {
|
|
531
|
+
return inspection.active.intelligence ?? inspection.active.effort;
|
|
532
|
+
}
|
|
533
|
+
if (axis === "effort") {
|
|
534
|
+
return inspection.active.effort ?? inspection.active.intelligence;
|
|
535
|
+
}
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
function configurationValueMatches(actual, requested) {
|
|
539
|
+
const normalizedActual = normalizeConfigurationId(actual);
|
|
540
|
+
const normalizedRequested = normalizeConfigurationId(requested);
|
|
541
|
+
if (normalizedActual === normalizedRequested)
|
|
542
|
+
return true;
|
|
543
|
+
return configurationSemanticLabels(requested)
|
|
544
|
+
.some(label => normalizeConfigurationId(label) === normalizedActual);
|
|
545
|
+
}
|
|
546
|
+
function selectionEntries(selection) {
|
|
547
|
+
const entries = [];
|
|
548
|
+
for (const axis of CONFIGURATION_AXIS_ORDER) {
|
|
549
|
+
const value = selection[axis];
|
|
550
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
551
|
+
entries.push([axis, value.trim()]);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return entries;
|
|
555
|
+
}
|
|
556
|
+
function normalizeDesiredSelection(selection) {
|
|
557
|
+
const normalized = {};
|
|
558
|
+
for (const axis of ["model", "intelligence", "effort", "speed"]) {
|
|
559
|
+
const value = selection[axis]?.trim();
|
|
560
|
+
if (value !== undefined && value.length > 0)
|
|
561
|
+
normalized[axis] = value;
|
|
562
|
+
}
|
|
563
|
+
const modelVersion = (selection.modelVersion ?? selection.version)?.trim();
|
|
564
|
+
if (modelVersion !== undefined && modelVersion.length > 0) {
|
|
565
|
+
normalized.modelVersion = modelVersion;
|
|
566
|
+
}
|
|
567
|
+
return normalized;
|
|
568
|
+
}
|
|
569
|
+
function menuItemToOption(item) {
|
|
570
|
+
const option = {
|
|
571
|
+
id: normalizeConfigurationId(item.label),
|
|
572
|
+
label: item.label,
|
|
573
|
+
selected: item.checked === true
|
|
574
|
+
};
|
|
575
|
+
if (item.hasPopup !== undefined)
|
|
576
|
+
option.hasSubmenu = item.hasPopup;
|
|
577
|
+
return option;
|
|
578
|
+
}
|
|
579
|
+
function dedupeOptions(options) {
|
|
580
|
+
const seen = new Set();
|
|
581
|
+
return options.filter(option => {
|
|
582
|
+
const key = `${option.id}\u0000${option.label}`;
|
|
583
|
+
if (seen.has(key))
|
|
584
|
+
return false;
|
|
585
|
+
seen.add(key);
|
|
586
|
+
return true;
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function chatMenuLooksSimplified(items) {
|
|
590
|
+
const normalized = items.map(item => normalizeConfigurationId(item.label));
|
|
591
|
+
const simplified = ["instant", "medium", "high", "extra high", "pro"];
|
|
592
|
+
return simplified.filter(label => normalized.includes(label)).length >= 3;
|
|
593
|
+
}
|
|
594
|
+
function isConfigurationAxisRow(label) {
|
|
595
|
+
const normalized = normalizeForLabelMatch(label);
|
|
596
|
+
return Object.values(localeLabels.configurationAxes)
|
|
597
|
+
.flat()
|
|
598
|
+
.some(axis => {
|
|
599
|
+
const prefix = normalizeForLabelMatch(axis);
|
|
600
|
+
return normalized === prefix || normalized.startsWith(`${prefix} `);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
function normalizeConfigurationId(value) {
|
|
604
|
+
return normalizeForLabelMatch(value)
|
|
605
|
+
.replace(/^gpt[\s-]*/i, "gpt ")
|
|
606
|
+
.replace(/[_-]+/g, " ")
|
|
607
|
+
.replace(/\s+/g, " ")
|
|
608
|
+
.trim();
|
|
609
|
+
}
|
|
610
|
+
async function closeConfigurationMenus(page) {
|
|
611
|
+
await page.keyboard?.press?.("Escape");
|
|
612
|
+
await page.waitForTimeout?.(50);
|
|
613
|
+
await page.keyboard?.press?.("Escape");
|
|
614
|
+
}
|
|
615
|
+
async function clickIfUnique(locator) {
|
|
616
|
+
if (locator?.count === undefined || locator.click === undefined)
|
|
617
|
+
return false;
|
|
618
|
+
if (await locator.count().catch(() => 0) !== 1)
|
|
619
|
+
return false;
|
|
620
|
+
await locator.click();
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
async function configurationFailure(page, before, desired, selected, message, code, candidates = []) {
|
|
624
|
+
const data = {
|
|
625
|
+
requested: desired,
|
|
626
|
+
selected,
|
|
627
|
+
before,
|
|
628
|
+
after: before,
|
|
629
|
+
verified: false
|
|
630
|
+
};
|
|
631
|
+
return {
|
|
632
|
+
ok: false,
|
|
633
|
+
status: "unsupported",
|
|
634
|
+
data,
|
|
635
|
+
warnings: [],
|
|
636
|
+
blocker: {
|
|
637
|
+
kind: "selector_drift",
|
|
638
|
+
code,
|
|
639
|
+
fieldPath: "desired",
|
|
640
|
+
message,
|
|
641
|
+
candidates: candidates.map(label => ({ label })),
|
|
642
|
+
resumable: true
|
|
643
|
+
},
|
|
644
|
+
context: await contextFromPage(page, {
|
|
645
|
+
experience: before.experience,
|
|
646
|
+
selectorProfile: before.selectorProfile
|
|
647
|
+
})
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function escapeRegExp(value) {
|
|
651
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
652
|
+
}
|
|
653
|
+
function escapeAttributeValue(value) {
|
|
654
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
655
|
+
}
|
|
656
|
+
function forwardFailure(result) {
|
|
657
|
+
const forwarded = {
|
|
658
|
+
ok: false,
|
|
659
|
+
status: result.status,
|
|
660
|
+
warnings: result.warnings,
|
|
661
|
+
context: result.context
|
|
662
|
+
};
|
|
663
|
+
if (result.output_text !== undefined)
|
|
664
|
+
forwarded.output_text = result.output_text;
|
|
665
|
+
if (result.reportPath !== undefined)
|
|
666
|
+
forwarded.reportPath = result.reportPath;
|
|
667
|
+
if (result.error !== undefined)
|
|
668
|
+
forwarded.error = result.error;
|
|
669
|
+
if (result.blocker !== undefined)
|
|
670
|
+
forwarded.blocker = result.blocker;
|
|
671
|
+
if (result.steps !== undefined)
|
|
672
|
+
forwarded.steps = result.steps;
|
|
673
|
+
return forwarded;
|
|
674
|
+
}
|
|
@@ -6,7 +6,9 @@ export async function contextFromPage(page, partial = {}) {
|
|
|
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;
|
|
9
|
-
const title = typeof page.title === "function"
|
|
9
|
+
const title = typeof page.title === "function"
|
|
10
|
+
? await withTimeout(page.title(), 1000, "Timed out while reading page title.").catch(() => partial.title)
|
|
11
|
+
: partial.title;
|
|
10
12
|
const [turnCount, assistantTurnCount] = await Promise.all([
|
|
11
13
|
withTimeout(countPageMessages(page), 1000, "Timed out while counting page messages.").catch(() => partial.turnCount),
|
|
12
14
|
withTimeout(countPageMessages(page, "assistant"), 1000, "Timed out while counting assistant messages.").catch(() => partial.assistantTurnCount)
|