codex-chatgpt-control 0.5.0-alpha.1 → 0.5.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/contracts/v1/fixtures/backend-capabilities.json +1 -0
- package/contracts/v1/fixtures/command-descriptors.json +29 -2
- package/contracts/v1/fixtures/doctor-bridge-upload.json +2 -2
- package/contracts/v1/fixtures/doctor-scenario-preflight.json +26 -4
- package/contracts/v1/fixtures/files-attach-indeterminate.json +30 -0
- package/contracts/v1/fixtures/help-root.json +1 -1
- package/contracts/v1/fixtures/messages-stop-indeterminate.json +31 -0
- package/contracts/v1/fixtures/messages-stop-needs-confirmation.json +20 -0
- package/contracts/v1/fixtures/messages-stop-noop.json +19 -0
- package/contracts/v1/fixtures/messages-stop-success.json +21 -0
- package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +3 -3
- package/contracts/v1/fixtures/reports-create-redacted.json +3 -3
- package/contracts/v1/fixtures/reports-summarize-redacted.json +1 -1
- package/contracts/v1/fixtures/run-browser-bridge-blocker.json +12 -12
- package/contracts/v1/fixtures/stream-blocked.ndjson +2 -2
- package/contracts/v1/manifest.json +25 -0
- package/contracts/v1/parity-suite.json +19 -1
- package/dist/codex-chatgpt-control-backend.mjs +3654 -603
- package/dist/codex-chatgpt-control.bundle.mjs +3627 -546
- package/dist/src/backend/client.d.ts +2 -1
- package/dist/src/backend/client.js +1 -0
- package/dist/src/backend/protocol.d.ts +1 -1
- package/dist/src/backend/protocol.js +1 -0
- package/dist/src/backend/session.js +2 -0
- package/dist/src/browser/attach.d.ts +1 -0
- package/dist/src/browser/attach.js +69 -35
- package/dist/src/browser/chatgpt-url.d.ts +8 -0
- package/dist/src/browser/chatgpt-url.js +28 -0
- package/dist/src/browser/downloads.d.ts +1 -1
- package/dist/src/browser/downloads.js +13 -4
- package/dist/src/browser/page-state.js +211 -4
- package/dist/src/client.d.ts +2 -1
- package/dist/src/client.js +2 -1
- package/dist/src/commands/configuration.js +330 -73
- package/dist/src/commands/context.d.ts +5 -1
- package/dist/src/commands/context.js +2 -2
- package/dist/src/commands/conversation.js +13 -1
- package/dist/src/commands/doctor.js +24 -2
- package/dist/src/commands/experience.d.ts +1 -0
- package/dist/src/commands/experience.js +156 -23
- package/dist/src/commands/files.d.ts +1 -0
- package/dist/src/commands/files.js +877 -170
- package/dist/src/commands/messages.d.ts +2 -1
- package/dist/src/commands/messages.js +310 -2
- package/dist/src/commands/modes.js +197 -52
- package/dist/src/commands/registry.js +8 -0
- package/dist/src/commands/sequence.js +3 -1
- package/dist/src/commands/session.d.ts +4 -1
- package/dist/src/commands/session.js +34 -3
- package/dist/src/dom/generation-state.d.ts +6 -1
- package/dist/src/dom/generation-state.js +206 -45
- package/dist/src/dom/locale/am.d.ts +23 -1
- package/dist/src/dom/locale/am.js +23 -1
- package/dist/src/dom/locale/ar.d.ts +21 -1
- package/dist/src/dom/locale/ar.js +24 -4
- package/dist/src/dom/locale/bg.d.ts +19 -1
- package/dist/src/dom/locale/bg.js +22 -4
- package/dist/src/dom/locale/bn.d.ts +23 -1
- package/dist/src/dom/locale/bn.js +23 -1
- package/dist/src/dom/locale/bs.d.ts +17 -1
- package/dist/src/dom/locale/bs.js +21 -5
- package/dist/src/dom/locale/ca.d.ts +20 -1
- package/dist/src/dom/locale/ca.js +25 -6
- package/dist/src/dom/locale/cs.d.ts +17 -1
- package/dist/src/dom/locale/cs.js +17 -1
- package/dist/src/dom/locale/da.d.ts +17 -1
- package/dist/src/dom/locale/da.js +18 -2
- package/dist/src/dom/locale/de.d.ts +16 -1
- package/dist/src/dom/locale/de.js +18 -3
- package/dist/src/dom/locale/el.d.ts +18 -1
- package/dist/src/dom/locale/el.js +21 -4
- package/dist/src/dom/locale/en.d.ts +2 -1
- package/dist/src/dom/locale/en.js +2 -1
- package/dist/src/dom/locale/es-419.d.ts +18 -1
- package/dist/src/dom/locale/es-419.js +18 -1
- package/dist/src/dom/locale/es-ES.d.ts +18 -1
- package/dist/src/dom/locale/es-ES.js +18 -1
- package/dist/src/dom/locale/et.d.ts +16 -1
- package/dist/src/dom/locale/et.js +16 -1
- package/dist/src/dom/locale/fa.d.ts +21 -1
- package/dist/src/dom/locale/fa.js +23 -3
- package/dist/src/dom/locale/fi.d.ts +18 -1
- package/dist/src/dom/locale/fi.js +18 -1
- package/dist/src/dom/locale/fr-CA.d.ts +15 -1
- package/dist/src/dom/locale/fr-CA.js +15 -1
- package/dist/src/dom/locale/fr-FR.d.ts +18 -1
- package/dist/src/dom/locale/fr-FR.js +22 -5
- package/dist/src/dom/locale/gu.d.ts +23 -1
- package/dist/src/dom/locale/gu.js +27 -5
- package/dist/src/dom/locale/hi.d.ts +23 -1
- package/dist/src/dom/locale/hi.js +25 -3
- package/dist/src/dom/locale/hr.d.ts +16 -1
- package/dist/src/dom/locale/hr.js +19 -4
- package/dist/src/dom/locale/hu.d.ts +17 -1
- package/dist/src/dom/locale/hu.js +20 -4
- package/dist/src/dom/locale/hy.d.ts +23 -1
- package/dist/src/dom/locale/hy.js +26 -4
- package/dist/src/dom/locale/id.d.ts +17 -1
- package/dist/src/dom/locale/id.js +19 -3
- package/dist/src/dom/locale/index.js +1 -0
- package/dist/src/dom/locale/is.d.ts +21 -1
- package/dist/src/dom/locale/is.js +21 -1
- package/dist/src/dom/locale/it.d.ts +15 -1
- package/dist/src/dom/locale/it.js +20 -6
- package/dist/src/dom/locale/ja.d.ts +19 -1
- package/dist/src/dom/locale/ja.js +23 -5
- package/dist/src/dom/locale/ka.d.ts +23 -1
- package/dist/src/dom/locale/ka.js +23 -1
- package/dist/src/dom/locale/kk.d.ts +23 -1
- package/dist/src/dom/locale/kk.js +25 -3
- package/dist/src/dom/locale/kn.d.ts +22 -1
- package/dist/src/dom/locale/kn.js +26 -5
- package/dist/src/dom/locale/ko.d.ts +17 -1
- package/dist/src/dom/locale/ko.js +17 -1
- package/dist/src/dom/locale/lt.d.ts +18 -1
- package/dist/src/dom/locale/lt.js +20 -3
- package/dist/src/dom/locale/lv.d.ts +18 -1
- package/dist/src/dom/locale/lv.js +18 -1
- package/dist/src/dom/locale/mk.d.ts +22 -1
- package/dist/src/dom/locale/mk.js +26 -5
- package/dist/src/dom/locale/ml.d.ts +23 -1
- package/dist/src/dom/locale/ml.js +25 -3
- package/dist/src/dom/locale/mn.d.ts +23 -1
- package/dist/src/dom/locale/mn.js +23 -1
- package/dist/src/dom/locale/mr.d.ts +23 -1
- package/dist/src/dom/locale/mr.js +25 -3
- package/dist/src/dom/locale/ms.d.ts +20 -1
- package/dist/src/dom/locale/ms.js +20 -1
- package/dist/src/dom/locale/my.d.ts +22 -1
- package/dist/src/dom/locale/my.js +24 -3
- package/dist/src/dom/locale/nb.d.ts +17 -1
- package/dist/src/dom/locale/nb.js +17 -1
- package/dist/src/dom/locale/nl.d.ts +20 -2
- package/dist/src/dom/locale/nl.js +22 -4
- package/dist/src/dom/locale/pa.d.ts +23 -1
- package/dist/src/dom/locale/pa.js +27 -5
- package/dist/src/dom/locale/pl.d.ts +17 -1
- package/dist/src/dom/locale/pl.js +19 -3
- package/dist/src/dom/locale/pt-BR.d.ts +18 -1
- package/dist/src/dom/locale/pt-BR.js +20 -3
- package/dist/src/dom/locale/pt-PT.d.ts +18 -1
- package/dist/src/dom/locale/pt-PT.js +22 -5
- package/dist/src/dom/locale/ro.d.ts +15 -1
- package/dist/src/dom/locale/ro.js +18 -4
- package/dist/src/dom/locale/ru.d.ts +23 -1
- package/dist/src/dom/locale/ru.js +24 -2
- package/dist/src/dom/locale/sk.d.ts +17 -1
- package/dist/src/dom/locale/sk.js +19 -3
- package/dist/src/dom/locale/sl.d.ts +21 -1
- package/dist/src/dom/locale/sl.js +24 -4
- package/dist/src/dom/locale/so.d.ts +22 -1
- package/dist/src/dom/locale/so.js +24 -3
- package/dist/src/dom/locale/sq.d.ts +21 -1
- package/dist/src/dom/locale/sq.js +25 -5
- package/dist/src/dom/locale/sr.d.ts +24 -1
- package/dist/src/dom/locale/sr.js +25 -2
- package/dist/src/dom/locale/sv.d.ts +16 -1
- package/dist/src/dom/locale/sv.js +16 -1
- package/dist/src/dom/locale/sw.d.ts +18 -1
- package/dist/src/dom/locale/sw.js +18 -1
- package/dist/src/dom/locale/ta.d.ts +23 -1
- package/dist/src/dom/locale/ta.js +27 -5
- package/dist/src/dom/locale/te.d.ts +23 -1
- package/dist/src/dom/locale/te.js +27 -5
- package/dist/src/dom/locale/th.d.ts +11 -1
- package/dist/src/dom/locale/th.js +11 -1
- package/dist/src/dom/locale/tl.d.ts +8 -1
- package/dist/src/dom/locale/tl.js +8 -1
- package/dist/src/dom/locale/tr.d.ts +21 -1
- package/dist/src/dom/locale/tr.js +23 -3
- package/dist/src/dom/locale/types.d.ts +1 -1
- package/dist/src/dom/locale/uk.d.ts +23 -1
- package/dist/src/dom/locale/uk.js +25 -3
- package/dist/src/dom/locale/ur.d.ts +22 -1
- package/dist/src/dom/locale/ur.js +25 -4
- package/dist/src/dom/locale/vi.d.ts +22 -1
- package/dist/src/dom/locale/vi.js +25 -4
- package/dist/src/dom/locale/zh-HK.d.ts +21 -1
- package/dist/src/dom/locale/zh-HK.js +23 -3
- package/dist/src/dom/locale/zh-Hans.d.ts +23 -1
- package/dist/src/dom/locale/zh-Hans.js +27 -5
- package/dist/src/dom/locale/zh-TW.d.ts +23 -1
- package/dist/src/dom/locale/zh-TW.js +25 -3
- package/dist/src/dom/menus.js +26 -2
- package/dist/src/dom/selectors.d.ts +1 -0
- package/dist/src/dom/selectors.js +9 -1
- package/dist/src/dom/wait-snapshot.d.ts +1 -0
- package/dist/src/dom/wait-snapshot.js +45 -22
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +2 -2
- package/dist/src/platform/local-paths.d.ts +1 -0
- package/dist/src/platform/local-paths.js +7 -3
- package/dist/src/safety/risk.d.ts +1 -0
- package/dist/src/safety/risk.js +1 -0
- package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +10 -1
- package/dist/src/scripts/apply-intelligence-locale-captures.js +219 -7
- package/dist/src/scripts/capture-intelligence-locales.d.ts +119 -1
- package/dist/src/scripts/capture-intelligence-locales.js +653 -232
- package/dist/src/scripts/capture-surface-profile.d.ts +3 -1
- package/dist/src/scripts/capture-surface-profile.js +75 -25
- package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
- package/dist/src/scripts/live-smoke/scenarios.js +321 -13
- package/dist/src/scripts/live-smoke.js +1 -0
- package/dist/src/scripts/locale-capture/surface-graph.d.ts +31 -0
- package/dist/src/scripts/locale-capture/surface-graph.js +50 -0
- package/dist/src/scripts/release-canary-module.d.ts +20 -0
- package/dist/src/scripts/release-canary-module.js +86 -0
- package/dist/src/types.d.ts +30 -6
- package/package.json +8 -2
- package/references/2026-07-16-chat-work-surfaces.md +13 -0
- package/references/2026-08-08-model-selection-locale-sweep.md +32 -0
- package/references/backend-protocol.md +22 -7
- package/references/language-coverage.md +24 -13
- package/references/localization.md +36 -14
- package/references/python-parity.md +9 -3
- package/references/troubleshooting.md +26 -4
|
@@ -33,6 +33,8 @@ const REQUIRED_LOCALE_KEYS = [
|
|
|
33
33
|
];
|
|
34
34
|
const REQUIRED_TOOL_IDS = ["web_search", "deep_research", "create_image"];
|
|
35
35
|
const REQUIRED_MODE_OPTION_IDS = ["latest", "instant", "thinking", "extended", "medium", "high", "extraHigh", "pro"];
|
|
36
|
+
const REQUIRED_CONFIGURATION_AXIS_IDS = ["power", "model", "intelligence", "effort", "speed", "advanced"];
|
|
37
|
+
const REQUIRED_CONFIGURATION_OPTION_IDS = ["instant", "light", "medium", "high", "extraHigh", "max", "ultra", "pro", "standard", "fast"];
|
|
36
38
|
export async function doctor(env, args = {}) {
|
|
37
39
|
const wanted = args.check ?? DEFAULT_CHECKS;
|
|
38
40
|
const checks = {};
|
|
@@ -232,8 +234,14 @@ function localizationCheck(env) {
|
|
|
232
234
|
const requiredKeysMissing = REQUIRED_LOCALE_KEYS.filter(key => localeLabels[key].length === 0);
|
|
233
235
|
const missingToolIds = REQUIRED_TOOL_IDS.filter(id => (localeLabels.tools[id]?.length ?? 0) === 0);
|
|
234
236
|
const missingModeOptionIds = REQUIRED_MODE_OPTION_IDS.filter(id => (localeLabels.modeOptions[id]?.length ?? 0) === 0);
|
|
237
|
+
const missingConfigurationAxisIds = REQUIRED_CONFIGURATION_AXIS_IDS
|
|
238
|
+
.filter(id => (localeLabels.configurationAxes[id]?.length ?? 0) === 0);
|
|
239
|
+
const missingConfigurationOptionIds = REQUIRED_CONFIGURATION_OPTION_IDS
|
|
240
|
+
.filter(id => (localeLabels.configurationOptions[id]?.length ?? 0) === 0);
|
|
235
241
|
const toolIds = Object.keys(localeLabels.tools);
|
|
236
242
|
const modeOptionIds = Object.keys(localeLabels.modeOptions);
|
|
243
|
+
const configurationAxisIds = Object.keys(localeLabels.configurationAxes);
|
|
244
|
+
const configurationOptionIds = Object.keys(localeLabels.configurationOptions);
|
|
237
245
|
const proAliases = [...localeLabels.modeOptions.pro];
|
|
238
246
|
const supportsProExtendedAlias = proAliases.some(alias => /pro\s*(?:•\s*)?extended/i.test(alias))
|
|
239
247
|
&& proAliases.some(alias => /extended\s+pro/i.test(alias));
|
|
@@ -250,17 +258,26 @@ function localizationCheck(env) {
|
|
|
250
258
|
&& localeLabels.sendButton[0] === "Send prompt"
|
|
251
259
|
&& localeLabels.modeLabels.includes("Thinking")
|
|
252
260
|
&& localeLabels.modeOptions.pro?.[0] === "Pro"
|
|
261
|
+
&& localeLabels.configurationAxes.power?.[0] === "Power"
|
|
262
|
+
&& localeLabels.configurationAxes.advanced?.[0] === "Advanced"
|
|
263
|
+
&& localeLabels.configurationOptions.ultra?.[0] === "Ultra"
|
|
253
264
|
&& localeLabels.tools.web_search?.[0] === "Web search";
|
|
254
265
|
const labelCandidateCount = REQUIRED_LOCALE_KEYS.reduce((total, key) => total + localeLabels[key].length, 0)
|
|
255
266
|
+ Object.values(localeLabels.tools).reduce((total, values) => total + values.length, 0)
|
|
256
|
-
+ Object.values(localeLabels.modeOptions).reduce((total, values) => total + values.length, 0)
|
|
267
|
+
+ Object.values(localeLabels.modeOptions).reduce((total, values) => total + values.length, 0)
|
|
268
|
+
+ Object.values(localeLabels.configurationAxes).reduce((total, values) => total + values.length, 0)
|
|
269
|
+
+ Object.values(localeLabels.configurationOptions).reduce((total, values) => total + values.length, 0);
|
|
257
270
|
const details = {
|
|
258
271
|
englishCanonicalPresent,
|
|
259
272
|
requiredKeysMissing,
|
|
260
273
|
missingToolIds,
|
|
261
274
|
missingModeOptionIds,
|
|
275
|
+
missingConfigurationAxisIds,
|
|
276
|
+
missingConfigurationOptionIds,
|
|
262
277
|
toolIds,
|
|
263
278
|
modeOptionIds,
|
|
279
|
+
configurationAxisIds,
|
|
280
|
+
configurationOptionIds,
|
|
264
281
|
proAliases,
|
|
265
282
|
supportsProExtendedAlias,
|
|
266
283
|
runningStateLabelCoverage,
|
|
@@ -268,7 +285,12 @@ function localizationCheck(env) {
|
|
|
268
285
|
pageAvailable: env.page !== undefined,
|
|
269
286
|
runtimeSelectorCoverage: "registry_only_stage_2"
|
|
270
287
|
};
|
|
271
|
-
if (englishCanonicalPresent
|
|
288
|
+
if (englishCanonicalPresent
|
|
289
|
+
&& requiredKeysMissing.length === 0
|
|
290
|
+
&& missingToolIds.length === 0
|
|
291
|
+
&& missingModeOptionIds.length === 0
|
|
292
|
+
&& missingConfigurationAxisIds.length === 0
|
|
293
|
+
&& missingConfigurationOptionIds.length === 0) {
|
|
272
294
|
const runningStateMessage = runningStateLocalizationSupport === "complete"
|
|
273
295
|
? "running-state labels are present for every registered non-English locale"
|
|
274
296
|
: runningStateLocalizationSupport === "partial"
|
|
@@ -4,6 +4,7 @@ type SurfaceSnapshot = {
|
|
|
4
4
|
composerLabels: string[];
|
|
5
5
|
mainControls: string[];
|
|
6
6
|
mainText: string;
|
|
7
|
+
selectedSurfaceLabels?: string[];
|
|
7
8
|
};
|
|
8
9
|
export declare function detectExperience(env: RuntimeEnv, args?: DetectExperienceArgs): Promise<CommandResult<DetectExperienceData>>;
|
|
9
10
|
export declare function openExperience(env: RuntimeEnv, args: OpenExperienceArgs): Promise<CommandResult<OpenExperienceData>>;
|
|
@@ -3,6 +3,9 @@ import { localeLabels } from "../dom/locale-labels.js";
|
|
|
3
3
|
import { normalizeForLabelMatch, visibleLabelMatches } from "../dom/label-match.js";
|
|
4
4
|
import { contextFromPage } from "./context.js";
|
|
5
5
|
import { ensurePage } from "./session.js";
|
|
6
|
+
const CHATGPT_HOME = "https://chatgpt.com/";
|
|
7
|
+
const EXPERIENCE_CONTROL_DISCOVERY_TIMEOUT_MS = 15_000;
|
|
8
|
+
const EXPERIENCE_POLL_MS = 250;
|
|
6
9
|
export async function detectExperience(env, args = {}) {
|
|
7
10
|
void args;
|
|
8
11
|
const boot = await ensurePage(env);
|
|
@@ -43,14 +46,50 @@ export async function openExperience(env, args) {
|
|
|
43
46
|
}));
|
|
44
47
|
}
|
|
45
48
|
const labels = localeLabels.experienceOptions[args.experience];
|
|
46
|
-
if (!await clickUniqueExperienceControl(page, labels)) {
|
|
47
|
-
return experienceSelectorDrift(page, `No unique visible ChatGPT ${args.experience === "work" ? "Work" : "Chat"} surface control was found.`, before);
|
|
48
|
-
}
|
|
49
49
|
const timeoutMs = args.timeoutMs ?? 30000;
|
|
50
|
-
const
|
|
50
|
+
const discoveryAttempts = pollAttempts(Math.min(timeoutMs, EXPERIENCE_CONTROL_DISCOVERY_TIMEOUT_MS), EXPERIENCE_POLL_MS);
|
|
51
|
+
let observed = before;
|
|
52
|
+
let controlClicked = await clickUniqueExperienceControl(page, labels);
|
|
53
|
+
if (!controlClicked && await navigateConversationToSurfaceHome(page, args.timeoutMs)) {
|
|
54
|
+
observed = detectExperienceFromSnapshot(await readSurfaceSnapshot(page));
|
|
55
|
+
if (observed.experience === args.experience) {
|
|
56
|
+
return resultOk({
|
|
57
|
+
experience: args.experience,
|
|
58
|
+
previousExperience: before.experience,
|
|
59
|
+
changed: true,
|
|
60
|
+
selectorProfile: observed.selectorProfile
|
|
61
|
+
}, await contextFromPage(page, {
|
|
62
|
+
experience: observed.experience,
|
|
63
|
+
selectorProfile: observed.selectorProfile
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
controlClicked = await clickUniqueExperienceControl(page, labels);
|
|
67
|
+
}
|
|
68
|
+
// session.bootstrap can verify the composer before the Chat/Work radio has
|
|
69
|
+
// hydrated. Give the scoped surface control a short bounded discovery
|
|
70
|
+
// window instead of reporting selector drift from that transient state.
|
|
71
|
+
for (let attempt = 1; !controlClicked && attempt < discoveryAttempts; attempt += 1) {
|
|
72
|
+
await page.waitForTimeout?.(EXPERIENCE_POLL_MS);
|
|
73
|
+
observed = detectExperienceFromSnapshot(await readSurfaceSnapshot(page));
|
|
74
|
+
if (observed.experience === args.experience) {
|
|
75
|
+
return resultOk({
|
|
76
|
+
experience: args.experience,
|
|
77
|
+
previousExperience: before.experience,
|
|
78
|
+
changed: true,
|
|
79
|
+
selectorProfile: observed.selectorProfile
|
|
80
|
+
}, await contextFromPage(page, {
|
|
81
|
+
experience: observed.experience,
|
|
82
|
+
selectorProfile: observed.selectorProfile
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
controlClicked = await clickUniqueExperienceControl(page, labels);
|
|
86
|
+
}
|
|
87
|
+
if (!controlClicked) {
|
|
88
|
+
return experienceSelectorDrift(page, `No unique visible ChatGPT ${args.experience === "work" ? "Work" : "Chat"} surface control was found.`, observed);
|
|
89
|
+
}
|
|
51
90
|
let after = before;
|
|
52
|
-
|
|
53
|
-
await page.waitForTimeout?.(
|
|
91
|
+
for (let attempt = 0; attempt < pollAttempts(timeoutMs, EXPERIENCE_POLL_MS); attempt += 1) {
|
|
92
|
+
await page.waitForTimeout?.(EXPERIENCE_POLL_MS);
|
|
54
93
|
after = detectExperienceFromSnapshot(await readSurfaceSnapshot(page));
|
|
55
94
|
if (after.experience === args.experience) {
|
|
56
95
|
return resultOk({
|
|
@@ -86,12 +125,39 @@ export async function openExperience(env, args) {
|
|
|
86
125
|
return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
|
|
87
126
|
}
|
|
88
127
|
}
|
|
128
|
+
function pollAttempts(timeoutMs, pollMs) {
|
|
129
|
+
return Math.max(1, Math.ceil(Math.max(0, timeoutMs) / pollMs));
|
|
130
|
+
}
|
|
131
|
+
async function navigateConversationToSurfaceHome(page, timeoutMs) {
|
|
132
|
+
if (page.goto === undefined || page.url === undefined)
|
|
133
|
+
return false;
|
|
134
|
+
const currentUrl = await Promise.resolve(page.url()).catch(() => "");
|
|
135
|
+
if (!/^https:\/\/chatgpt\.com\/c\//i.test(currentUrl))
|
|
136
|
+
return false;
|
|
137
|
+
await page.goto(CHATGPT_HOME, {
|
|
138
|
+
waitUntil: "domcontentloaded",
|
|
139
|
+
timeout: timeoutMs ?? 30000
|
|
140
|
+
});
|
|
141
|
+
await page.waitForTimeout?.(500);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
89
144
|
export function detectExperienceFromSnapshot(snapshot) {
|
|
90
145
|
const evidence = [];
|
|
91
146
|
const composerLabels = snapshot.composerLabels.map(normalizeForLabelMatch);
|
|
92
147
|
const controls = snapshot.mainControls.map(normalizeForLabelMatch);
|
|
93
148
|
const mainText = normalizeForLabelMatch(snapshot.mainText);
|
|
149
|
+
const selectedSurfaceLabels = (snapshot.selectedSurfaceLabels ?? []).map(normalizeForLabelMatch);
|
|
94
150
|
const url = snapshot.url.toLowerCase();
|
|
151
|
+
const selectedWork = matchingLabels(selectedSurfaceLabels, localeLabels.experienceOptions.work);
|
|
152
|
+
const selectedChat = matchingLabels(selectedSurfaceLabels, localeLabels.experienceOptions.chat);
|
|
153
|
+
const workSurfaceSelected = selectedWork.length > 0 && selectedChat.length === 0;
|
|
154
|
+
const chatSurfaceSelected = selectedChat.length > 0 && selectedWork.length === 0;
|
|
155
|
+
if (workSurfaceSelected) {
|
|
156
|
+
evidence.push({ source: "control", label: "Work surface selected" });
|
|
157
|
+
}
|
|
158
|
+
else if (chatSurfaceSelected) {
|
|
159
|
+
evidence.push({ source: "control", label: "Chat surface selected" });
|
|
160
|
+
}
|
|
95
161
|
const workComposer = matchingLabels(composerLabels, localeLabels.workComposerTextbox);
|
|
96
162
|
for (const label of workComposer) {
|
|
97
163
|
evidence.push({ source: "composer", label });
|
|
@@ -106,6 +172,18 @@ export function detectExperienceFromSnapshot(snapshot) {
|
|
|
106
172
|
if (workAxisCount >= 2) {
|
|
107
173
|
evidence.push({ source: "control", label: `Work configuration axes (${workAxisCount}/3)` });
|
|
108
174
|
}
|
|
175
|
+
const workConfigurationOpener = controls.some(label => /\b(?:gpt[\s-]?\d|\d+(?:\.\d+)+|sol|luna|terra)\b/i.test(label)
|
|
176
|
+
&& hasAnyLabel([label], [
|
|
177
|
+
...localeLabels.configurationOptions.light,
|
|
178
|
+
...localeLabels.configurationOptions.medium,
|
|
179
|
+
...localeLabels.configurationOptions.high,
|
|
180
|
+
...localeLabels.configurationOptions.extraHigh,
|
|
181
|
+
...localeLabels.configurationOptions.max,
|
|
182
|
+
...localeLabels.configurationOptions.ultra,
|
|
183
|
+
]));
|
|
184
|
+
if (workConfigurationOpener) {
|
|
185
|
+
evidence.push({ source: "control", label: "Work configuration opener" });
|
|
186
|
+
}
|
|
109
187
|
if (/\/work(?:\/|$|\?)/.test(url)) {
|
|
110
188
|
evidence.push({ source: "url", label: snapshot.url });
|
|
111
189
|
}
|
|
@@ -113,15 +191,21 @@ export function detectExperienceFromSnapshot(snapshot) {
|
|
|
113
191
|
evidence.push({ source: "heading", label: "Work composer copy" });
|
|
114
192
|
}
|
|
115
193
|
const workScore = workComposer.length * 4
|
|
194
|
+
+ (workSurfaceSelected ? 10 : 0)
|
|
116
195
|
+ (workAxisCount >= 2 ? 4 : 0)
|
|
196
|
+
// The active Work task drops the Chat/Work radio and keeps the shared
|
|
197
|
+
// "Chat with ChatGPT" textbox name. Its compound model + effort opener is
|
|
198
|
+
// therefore strong enough to disambiguate that continuation surface.
|
|
199
|
+
+ (workConfigurationOpener ? 6 : 0)
|
|
117
200
|
+ (/\/work(?:\/|$|\?)/.test(url) ? 3 : 0)
|
|
118
201
|
+ (containsAny(mainText, ["work on something else", "work on anything"]) ? 2 : 0);
|
|
119
|
-
const chatScore = chatComposer.length * 4
|
|
202
|
+
const chatScore = chatComposer.length * 4
|
|
203
|
+
+ (chatSurfaceSelected ? 10 : 0);
|
|
120
204
|
let experience = "unknown";
|
|
121
205
|
let confidence = "low";
|
|
122
206
|
if (workScore > chatScore && workScore >= 4) {
|
|
123
207
|
experience = "work";
|
|
124
|
-
confidence = workScore >= 7 ? "high" : "medium";
|
|
208
|
+
confidence = workSurfaceSelected || workScore >= 7 ? "high" : "medium";
|
|
125
209
|
}
|
|
126
210
|
else if (chatScore > workScore && chatScore >= 4) {
|
|
127
211
|
experience = "chat";
|
|
@@ -135,17 +219,28 @@ export async function readSurfaceSnapshot(page) {
|
|
|
135
219
|
? await Promise.resolve(page.url()).catch(() => "")
|
|
136
220
|
: "";
|
|
137
221
|
if (typeof page.evaluate !== "function") {
|
|
138
|
-
return { url, composerLabels: [], mainControls: [], mainText: "" };
|
|
222
|
+
return { url, composerLabels: [], mainControls: [], mainText: "", selectedSurfaceLabels: [] };
|
|
139
223
|
}
|
|
140
|
-
const snapshot = await page.evaluate(() => {
|
|
224
|
+
const snapshot = await page.evaluate((surfaceOptionLabels) => {
|
|
141
225
|
const visible = (element) => {
|
|
142
226
|
const html = element;
|
|
143
227
|
const rect = html.getBoundingClientRect?.();
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
228
|
+
if (rect !== undefined && (rect.width <= 0 || rect.height <= 0))
|
|
229
|
+
return false;
|
|
230
|
+
let current = element;
|
|
231
|
+
while (current !== null) {
|
|
232
|
+
if (current.hasAttribute?.("inert") || current.getAttribute?.("aria-hidden") === "true") {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const style = typeof window !== "undefined"
|
|
236
|
+
? window.getComputedStyle?.(current)
|
|
237
|
+
: undefined;
|
|
238
|
+
if (style?.display === "none" || style?.visibility === "hidden" || style?.opacity === "0") {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
current = current.parentElement ?? null;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
149
244
|
};
|
|
150
245
|
const labelFor = (element) => {
|
|
151
246
|
const html = element;
|
|
@@ -156,6 +251,8 @@ export async function readSurfaceSnapshot(page) {
|
|
|
156
251
|
?? "";
|
|
157
252
|
};
|
|
158
253
|
const normalize = (value) => value.replace(/\s+/g, " ").trim();
|
|
254
|
+
const normalizeComparable = (value) => normalize(value).toLocaleLowerCase();
|
|
255
|
+
const wantedSurfaceLabels = new Set(surfaceOptionLabels.map(normalizeComparable));
|
|
159
256
|
const composerRoots = Array.from(document.querySelectorAll("main form, main [data-testid*='composer' i], main [class*='composer' i]"));
|
|
160
257
|
const composerNodes = composerRoots.flatMap(root => [
|
|
161
258
|
root,
|
|
@@ -183,8 +280,17 @@ export async function readSurfaceSnapshot(page) {
|
|
|
183
280
|
.filter(visible)
|
|
184
281
|
.slice(0, 32);
|
|
185
282
|
const mainText = normalize(surfaceTextNodes.map(labelFor).join(" ")).slice(0, 2000);
|
|
186
|
-
|
|
187
|
-
|
|
283
|
+
const selectedSurfaceLabels = Array.from(new Set(Array.from(document.querySelectorAll("[role='radio'][aria-checked='true'], [role='radio'][data-state='checked'], input[type='radio']:checked"))
|
|
284
|
+
.filter(visible)
|
|
285
|
+
.map(labelFor)
|
|
286
|
+
.map(normalize)
|
|
287
|
+
.filter(label => wantedSurfaceLabels.has(normalizeComparable(label)))))
|
|
288
|
+
.slice(0, 4);
|
|
289
|
+
return { composerLabels, mainControls, mainText, selectedSurfaceLabels };
|
|
290
|
+
}, [
|
|
291
|
+
...localeLabels.experienceOptions.chat,
|
|
292
|
+
...localeLabels.experienceOptions.work,
|
|
293
|
+
]).catch(() => ({ composerLabels: [], mainControls: [], mainText: "", selectedSurfaceLabels: [] }));
|
|
188
294
|
return { url, ...snapshot };
|
|
189
295
|
}
|
|
190
296
|
function profileFromSnapshot(snapshot, experience) {
|
|
@@ -220,7 +326,7 @@ function profileFromSnapshot(snapshot, experience) {
|
|
|
220
326
|
}
|
|
221
327
|
async function clickUniqueExperienceControl(page, labels) {
|
|
222
328
|
for (const label of labels) {
|
|
223
|
-
for (const role of ["button", "menuitem", "tab", "link"]) {
|
|
329
|
+
for (const role of ["radio", "button", "menuitem", "tab", "link"]) {
|
|
224
330
|
if (await clickIfUnique(page.getByRole?.(role, { name: label, exact: true }))) {
|
|
225
331
|
return true;
|
|
226
332
|
}
|
|
@@ -232,12 +338,39 @@ async function clickUniqueExperienceControl(page, labels) {
|
|
|
232
338
|
return page.evaluate((wantedLabels) => {
|
|
233
339
|
const normalize = (value) => value.replace(/\s+/g, " ").trim().toLocaleLowerCase();
|
|
234
340
|
const wanted = new Set(wantedLabels.map(normalize));
|
|
235
|
-
const
|
|
236
|
-
|
|
341
|
+
const visible = (element) => {
|
|
342
|
+
const html = element;
|
|
343
|
+
const rect = html.getBoundingClientRect?.();
|
|
344
|
+
if (rect !== undefined && (rect.width <= 0 || rect.height <= 0))
|
|
345
|
+
return false;
|
|
346
|
+
let current = element;
|
|
347
|
+
while (current !== null) {
|
|
348
|
+
if (current.hasAttribute?.("inert") || current.getAttribute?.("aria-hidden") === "true") {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
const style = typeof window !== "undefined"
|
|
352
|
+
? window.getComputedStyle?.(current)
|
|
353
|
+
: undefined;
|
|
354
|
+
if (style?.display === "none" || style?.visibility === "hidden" || style?.opacity === "0") {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
current = current.parentElement ?? null;
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
};
|
|
361
|
+
const labelFor = (node) => {
|
|
237
362
|
const html = node;
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
363
|
+
const inputLabels = "labels" in node
|
|
364
|
+
? Array.from(node.labels ?? []).map(label => label.innerText).join(" ")
|
|
365
|
+
: "";
|
|
366
|
+
return node.getAttribute("aria-label")
|
|
367
|
+
|| inputLabels
|
|
368
|
+
|| html.innerText
|
|
369
|
+
|| node.textContent
|
|
370
|
+
|| "";
|
|
371
|
+
};
|
|
372
|
+
const nodes = Array.from(document.querySelectorAll("[role='radio'], input[type='radio'], header button, header [role='button'], header [role='tab'], main [role='menuitem'], main [role='option']"));
|
|
373
|
+
const matches = nodes.filter(node => visible(node) && wanted.has(normalize(labelFor(node))));
|
|
241
374
|
if (matches.length !== 1)
|
|
242
375
|
return false;
|
|
243
376
|
matches[0].click();
|
|
@@ -3,3 +3,4 @@ export declare function validateAttachPaths(paths: string[]): Promise<AttachedFi
|
|
|
3
3
|
export declare function preflightFiles(env: RuntimeEnv, args: FilePreflightArgs): Promise<CommandResult<FilePreflightData>>;
|
|
4
4
|
export declare function attachFiles(env: RuntimeEnv, args: AttachFilesArgs): Promise<CommandResult<AttachFilesData>>;
|
|
5
5
|
export declare function downloadLatestFile(env: RuntimeEnv, args: DownloadLatestArgs): Promise<CommandResult<DownloadedFile>>;
|
|
6
|
+
export declare function stripLocalizedDownloadPrefix(value: string, labels: readonly string[]): string;
|