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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { detectExperienceFromSnapshot, readSurfaceSnapshot } from "../commands/experience.js";
|
|
2
|
-
import type { BrowserLike, ConfigurationInspectionData, ExistingTabPolicy, SurfaceProfileFixture, SurfaceProfileSupportState } from "../types.js";
|
|
2
|
+
import type { BrowserLike, ChatGPTExperience, ConfigurationInspectionData, ExistingTabPolicy, SurfaceProfileFixture, SurfaceProfileSupportState } from "../types.js";
|
|
3
3
|
type CaptureRuntime = {
|
|
4
4
|
agent?: unknown;
|
|
5
5
|
browser?: BrowserLike;
|
|
@@ -16,6 +16,8 @@ type CaptureOptions = {
|
|
|
16
16
|
provenance: string;
|
|
17
17
|
tabId?: string;
|
|
18
18
|
ifMissing: NonNullable<ExistingTabPolicy["ifMissing"]>;
|
|
19
|
+
experience?: Exclude<ChatGPTExperience, "unknown">;
|
|
20
|
+
restoreExperience: boolean;
|
|
19
21
|
};
|
|
20
22
|
export declare function main(argv?: string[], runtime?: CaptureRuntime): Promise<number>;
|
|
21
23
|
export declare function buildSurfaceProfileDraft(options: Pick<CaptureOptions, "id" | "region" | "accountScope" | "planScope" | "workspaceScope" | "supportState" | "provenance">, locale: string, snapshot: Awaited<ReturnType<typeof readSurfaceSnapshot>>, detected: ReturnType<typeof detectExperienceFromSnapshot>, inspection: ConfigurationInspectionData, observedAt?: string): SurfaceProfileFixture;
|
|
@@ -2,7 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { attachChatGPTBrowser } from "../browser/attach.js";
|
|
4
4
|
import { inspectConfiguration } from "../commands/configuration.js";
|
|
5
|
-
import { detectExperienceFromSnapshot, readSurfaceSnapshot } from "../commands/experience.js";
|
|
5
|
+
import { detectExperienceFromSnapshot, openExperience, readSurfaceSnapshot } from "../commands/experience.js";
|
|
6
6
|
import { BROWSER_BRIDGE_REMEDIATION, BROWSER_BRIDGE_UNAVAILABLE_MESSAGE, toCommandResult } from "../errors.js";
|
|
7
7
|
const CHATGPT_HOME = "https://chatgpt.com/";
|
|
8
8
|
const DEFAULT_PROVENANCE = "Read-only sanitized capture from visible ChatGPT controls; review before committing.";
|
|
@@ -31,9 +31,12 @@ const USAGE = [
|
|
|
31
31
|
" --provenance Non-private provenance note.",
|
|
32
32
|
" --tab-id Claim an exact already-open ChatGPT tab.",
|
|
33
33
|
" --if-missing block|open|create. Default: block.",
|
|
34
|
+
" --experience Capture chat|work, switching visibly when needed.",
|
|
35
|
+
" --no-restore-experience Leave the requested surface selected. Default: restore.",
|
|
34
36
|
"",
|
|
35
|
-
"The capture
|
|
36
|
-
"
|
|
37
|
+
"The capture does not mutate model configuration. With --experience it visibly",
|
|
38
|
+
"switches panes and restores the prior pane by default. It writes a sanitized draft",
|
|
39
|
+
"locally and never records",
|
|
37
40
|
"prompts, responses, sidebar titles, account names, cookies, or network data."
|
|
38
41
|
].join("\n");
|
|
39
42
|
export async function main(argv = process.argv.slice(2), runtime = globalThis) {
|
|
@@ -79,27 +82,53 @@ export async function main(argv = process.argv.slice(2), runtime = globalThis) {
|
|
|
79
82
|
url: CHATGPT_HOME
|
|
80
83
|
});
|
|
81
84
|
env.page = attached.page;
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
const initial = detectExperienceFromSnapshot(await readSurfaceSnapshot(attached.page));
|
|
86
|
+
const previousExperience = initial.experience === "unknown" ? undefined : initial.experience;
|
|
87
|
+
if (options.experience !== undefined && previousExperience === undefined) {
|
|
88
|
+
throw new Error("Cannot safely switch surfaces because the initial Chat/Work pane could not be identified.");
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
if (options.experience !== undefined) {
|
|
92
|
+
const opened = await openExperience(env, { experience: options.experience, timeoutMs: 60_000 });
|
|
93
|
+
if (!opened.ok || opened.data?.experience !== options.experience) {
|
|
94
|
+
console.log(JSON.stringify(opened, null, 2));
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const snapshot = await readSurfaceSnapshot(attached.page);
|
|
99
|
+
const detected = detectExperienceFromSnapshot(snapshot);
|
|
100
|
+
const inspectionResult = await inspectConfiguration(env, { includeOptions: true });
|
|
101
|
+
if (!inspectionResult.ok || inspectionResult.data === undefined) {
|
|
102
|
+
console.log(JSON.stringify(inspectionResult, null, 2));
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
const locale = options.locale ?? await readDocumentLocale(attached.page) ?? "und";
|
|
106
|
+
const profile = buildSurfaceProfileDraft(options, locale, snapshot, detected, inspectionResult.data);
|
|
107
|
+
await mkdir(dirname(options.out), { recursive: true });
|
|
108
|
+
await writeFile(options.out, `${JSON.stringify(profile, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
ok: true,
|
|
111
|
+
status: "ok",
|
|
112
|
+
path: options.out,
|
|
113
|
+
id: profile.id,
|
|
114
|
+
experience: profile.expected.experience,
|
|
115
|
+
selectorProfile: profile.expected.selectorProfile,
|
|
116
|
+
supportState: profile.supportState,
|
|
117
|
+
restoreExperience: options.restoreExperience
|
|
118
|
+
}, null, 2));
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
if (options.experience !== undefined
|
|
123
|
+
&& options.restoreExperience
|
|
124
|
+
&& previousExperience !== undefined
|
|
125
|
+
&& previousExperience !== options.experience) {
|
|
126
|
+
const restored = await openExperience(env, { experience: previousExperience, timeoutMs: 60_000 });
|
|
127
|
+
if (!restored.ok || restored.data?.experience !== previousExperience) {
|
|
128
|
+
throw new Error(`Captured ${options.experience}, but failed to restore ${previousExperience}.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
88
131
|
}
|
|
89
|
-
const locale = options.locale ?? await readDocumentLocale(attached.page) ?? "und";
|
|
90
|
-
const profile = buildSurfaceProfileDraft(options, locale, snapshot, detected, inspectionResult.data);
|
|
91
|
-
await mkdir(dirname(options.out), { recursive: true });
|
|
92
|
-
await writeFile(options.out, `${JSON.stringify(profile, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
93
|
-
console.log(JSON.stringify({
|
|
94
|
-
ok: true,
|
|
95
|
-
status: "ok",
|
|
96
|
-
path: options.out,
|
|
97
|
-
id: profile.id,
|
|
98
|
-
experience: profile.expected.experience,
|
|
99
|
-
selectorProfile: profile.expected.selectorProfile,
|
|
100
|
-
supportState: profile.supportState
|
|
101
|
-
}, null, 2));
|
|
102
|
-
return 0;
|
|
103
132
|
}
|
|
104
133
|
catch (error) {
|
|
105
134
|
console.log(JSON.stringify(toCommandResult(error), null, 2));
|
|
@@ -184,6 +213,8 @@ export function parseArgs(argv) {
|
|
|
184
213
|
let provenance = DEFAULT_PROVENANCE;
|
|
185
214
|
let tabId;
|
|
186
215
|
let ifMissing = "block";
|
|
216
|
+
let experience;
|
|
217
|
+
let restoreExperience = true;
|
|
187
218
|
for (let index = 0; index < argv.length; index += 1) {
|
|
188
219
|
const arg = argv[index];
|
|
189
220
|
if (arg === undefined)
|
|
@@ -225,6 +256,15 @@ export function parseArgs(argv) {
|
|
|
225
256
|
case "--if-missing":
|
|
226
257
|
ifMissing = parseIfMissing(requiredValue(argv, ++index, arg));
|
|
227
258
|
break;
|
|
259
|
+
case "--experience":
|
|
260
|
+
experience = parseExperience(requiredValue(argv, ++index, arg));
|
|
261
|
+
break;
|
|
262
|
+
case "--restore-experience":
|
|
263
|
+
restoreExperience = true;
|
|
264
|
+
break;
|
|
265
|
+
case "--no-restore-experience":
|
|
266
|
+
restoreExperience = false;
|
|
267
|
+
break;
|
|
228
268
|
default:
|
|
229
269
|
throw new UsageError(`Unknown argument: ${arg}\n\n${USAGE}`);
|
|
230
270
|
}
|
|
@@ -253,7 +293,9 @@ export function parseArgs(argv) {
|
|
|
253
293
|
supportState,
|
|
254
294
|
provenance,
|
|
255
295
|
...(tabId === undefined ? {} : { tabId }),
|
|
256
|
-
ifMissing
|
|
296
|
+
ifMissing,
|
|
297
|
+
...(experience === undefined ? {} : { experience }),
|
|
298
|
+
restoreExperience
|
|
257
299
|
};
|
|
258
300
|
}
|
|
259
301
|
async function readDocumentLocale(page) {
|
|
@@ -289,6 +331,11 @@ function parseSupportState(value) {
|
|
|
289
331
|
}
|
|
290
332
|
throw new UsageError(`Invalid --support-state: ${value}`);
|
|
291
333
|
}
|
|
334
|
+
function parseExperience(value) {
|
|
335
|
+
if (value === "chat" || value === "work")
|
|
336
|
+
return value;
|
|
337
|
+
throw new UsageError("--experience must be chat or work.");
|
|
338
|
+
}
|
|
292
339
|
function parseIfMissing(value) {
|
|
293
340
|
if (value === "block" || value === "open" || value === "create")
|
|
294
341
|
return value;
|
|
@@ -299,6 +346,9 @@ function assertNormalizedSlug(value, flag) {
|
|
|
299
346
|
throw new UsageError(`${flag} must be a normalized lowercase slug without private names.`);
|
|
300
347
|
}
|
|
301
348
|
}
|
|
302
|
-
if (
|
|
349
|
+
if (typeof process !== "undefined"
|
|
350
|
+
&& Array.isArray(process.argv)
|
|
351
|
+
&& typeof process.argv[1] === "string"
|
|
352
|
+
&& import.meta.url === `file://${process.argv[1]}`) {
|
|
303
353
|
process.exitCode = await main();
|
|
304
354
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { AskReadData, CommandResult, ConfigurationInspectionData, ConfigurationSelection } from "../../types.js";
|
|
1
2
|
import type { LiveSmokeScenario } from "./types.js";
|
|
2
3
|
export declare const requiredScenarios: LiveSmokeScenario[];
|
|
3
4
|
export declare const optionalScenarios: LiveSmokeScenario[];
|
|
5
|
+
export declare function generatedFileAskCanProceed(result: CommandResult<AskReadData>): boolean;
|
|
6
|
+
export declare function chatActiveSelection(inspection: ConfigurationInspectionData): ConfigurationSelection;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdtemp, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { attachAskRead, attachFiles, askMessage, bootstrap, composeMessage, createChatGPT, copyResponse, downloadLatestAttachment, messageStatus, newThread, openThread, readLatest, runSequence, searchThreads, selectTool, setMode, submitMessage, twoTurnExchange, waitAndRead, waitForMessage } from "../../index.js";
|
|
5
|
-
import { readAssistantGenerationState } from "../../dom/generation-state.js";
|
|
4
|
+
import { attachAskRead, attachFiles, askMessage, bootstrap, composeMessage, createChatGPT, copyResponse, downloadLatestAttachment, messageStatus, newThread, openExperience, openThread, readLatest, runSequence, searchThreads, selectTool, setMode, stopGeneration, submitMessage, twoTurnExchange, waitAndRead, waitForMessage } from "../../index.js";
|
|
5
|
+
import { EMPTY_GENERATION_STATE, readAssistantGenerationState } from "../../dom/generation-state.js";
|
|
6
6
|
import { contextEnvFlag, contextEnvText } from "./harness.js";
|
|
7
7
|
export const requiredScenarios = [
|
|
8
8
|
scenario("bootstrap-new-tab", true, () => true, async (context, meta) => {
|
|
@@ -122,6 +122,163 @@ export const requiredScenarios = [
|
|
|
122
122
|
? pass(meta, result)
|
|
123
123
|
: fail(meta, result, { uploadRemediation });
|
|
124
124
|
}),
|
|
125
|
+
scenario("chat-work-expansion", true, () => true, async (context, meta) => {
|
|
126
|
+
const chatgpt = createChatGPT(clientOptionsFor(context));
|
|
127
|
+
const details = {};
|
|
128
|
+
let booted = false;
|
|
129
|
+
let terminal = syntheticCommand(meta.startedAt);
|
|
130
|
+
let failure;
|
|
131
|
+
try {
|
|
132
|
+
const boot = await chatgpt.session.bootstrap({ preferExistingTab: false, timeoutMs: 60000 });
|
|
133
|
+
terminal = asCommand(boot);
|
|
134
|
+
requireLiveCommand("session.bootstrap", boot);
|
|
135
|
+
booted = true;
|
|
136
|
+
const openedChat = await chatgpt.experience.open({ experience: "chat", timeoutMs: 60000 });
|
|
137
|
+
terminal = asCommand(openedChat);
|
|
138
|
+
requireLiveCommand("experience.open.chat", openedChat, openedChat.ok && openedChat.data?.experience === "chat");
|
|
139
|
+
const chatConfiguration = await chatgpt.configuration.inspect({
|
|
140
|
+
experience: "chat",
|
|
141
|
+
includeOptions: true,
|
|
142
|
+
timeoutMs: 60000
|
|
143
|
+
});
|
|
144
|
+
terminal = asCommand(chatConfiguration);
|
|
145
|
+
requireLiveCommand("configuration.inspect.chat", chatConfiguration, chatConfiguration.ok
|
|
146
|
+
&& chatConfiguration.data?.experience === "chat"
|
|
147
|
+
&& chatConfiguration.data.verified === true);
|
|
148
|
+
const chatDesired = chatActiveSelection(chatConfiguration.data);
|
|
149
|
+
requireLiveCommand("configuration.inspect.chat.active", chatConfiguration, Object.keys(chatDesired).length > 0);
|
|
150
|
+
const verifiedChatConfiguration = await chatgpt.configuration.apply({
|
|
151
|
+
experience: "chat",
|
|
152
|
+
desired: chatDesired,
|
|
153
|
+
strict: true,
|
|
154
|
+
timeoutMs: 60000
|
|
155
|
+
});
|
|
156
|
+
terminal = asCommand(verifiedChatConfiguration);
|
|
157
|
+
requireLiveCommand("configuration.apply.chat.noop", verifiedChatConfiguration, verifiedChatConfiguration.ok && verifiedChatConfiguration.data?.verified === true);
|
|
158
|
+
const openedWork = await chatgpt.experience.open({ experience: "work", timeoutMs: 60000 });
|
|
159
|
+
terminal = asCommand(openedWork);
|
|
160
|
+
requireLiveCommand("experience.open.work", openedWork, openedWork.ok && openedWork.data?.experience === "work");
|
|
161
|
+
const workConfiguration = await chatgpt.configuration.inspect({
|
|
162
|
+
experience: "work",
|
|
163
|
+
includeOptions: true,
|
|
164
|
+
timeoutMs: 60000
|
|
165
|
+
});
|
|
166
|
+
terminal = asCommand(workConfiguration);
|
|
167
|
+
requireLiveCommand("configuration.inspect.work", workConfiguration, workConfiguration.ok
|
|
168
|
+
&& workConfiguration.data?.experience === "work"
|
|
169
|
+
&& workConfiguration.data.verified === true
|
|
170
|
+
&& hasAxes(workConfiguration.data, ["model", "effort", "speed"]));
|
|
171
|
+
const workDesired = activeSelection(workConfiguration.data, ["model", "effort", "speed"]);
|
|
172
|
+
requireLiveCommand("configuration.inspect.work.active", workConfiguration, hasSelectionAxes(workDesired, ["model", "effort", "speed"]));
|
|
173
|
+
const verifiedWorkConfiguration = await chatgpt.configuration.apply({
|
|
174
|
+
experience: "work",
|
|
175
|
+
desired: workDesired,
|
|
176
|
+
strict: true,
|
|
177
|
+
timeoutMs: 60000
|
|
178
|
+
});
|
|
179
|
+
terminal = asCommand(verifiedWorkConfiguration);
|
|
180
|
+
requireLiveCommand("configuration.apply.work.noop", verifiedWorkConfiguration, verifiedWorkConfiguration.ok && verifiedWorkConfiguration.data?.verified === true);
|
|
181
|
+
const started = await chatgpt.work.start({
|
|
182
|
+
prompt: "Reply with exactly WORK_EXPANSION_START_OK.",
|
|
183
|
+
newTask: true,
|
|
184
|
+
wait: { timeoutMs: 180000, stableMs: 1500, pollMs: 750 },
|
|
185
|
+
read: { format: "normalized_text" },
|
|
186
|
+
timeoutMs: 60000
|
|
187
|
+
});
|
|
188
|
+
terminal = asCommand(started);
|
|
189
|
+
const startedText = started.data?.response?.text ?? started.output_text;
|
|
190
|
+
requireLiveCommand("work.start", started, started.ok && textEquals(startedText, "WORK_EXPANSION_START_OK"));
|
|
191
|
+
const status = await chatgpt.work.status({ includeArtifacts: true, maxPreviewChars: 200 });
|
|
192
|
+
terminal = asCommand(status);
|
|
193
|
+
requireLiveCommand("work.status", status, status.ok && status.data?.experience === "work");
|
|
194
|
+
const waited = await chatgpt.work.wait({
|
|
195
|
+
timeoutMs: 30000,
|
|
196
|
+
stableMs: 1000,
|
|
197
|
+
pollMs: 500,
|
|
198
|
+
responseContent: "metadata"
|
|
199
|
+
});
|
|
200
|
+
terminal = asCommand(waited);
|
|
201
|
+
requireLiveCommand("work.wait", waited, waited.ok && waited.data?.complete === true);
|
|
202
|
+
const read = await chatgpt.work.readLatest({ format: "normalized_text" });
|
|
203
|
+
terminal = asCommand(read);
|
|
204
|
+
requireLiveCommand("work.readLatest", read, read.ok && textEquals(read.data?.text, "WORK_EXPANSION_START_OK"));
|
|
205
|
+
const artifacts = await chatgpt.work.artifacts.listLatest({});
|
|
206
|
+
terminal = asCommand(artifacts);
|
|
207
|
+
requireLiveCommand("work.artifacts.listLatest", artifacts, artifacts.ok);
|
|
208
|
+
const steered = await chatgpt.work.steer({
|
|
209
|
+
prompt: "Reply with exactly WORK_EXPANSION_STEER_OK.",
|
|
210
|
+
wait: { timeoutMs: 180000, stableMs: 1500, pollMs: 750 },
|
|
211
|
+
read: { format: "normalized_text" },
|
|
212
|
+
timeoutMs: 60000
|
|
213
|
+
});
|
|
214
|
+
terminal = asCommand(steered);
|
|
215
|
+
requireLiveCommand("work.steer", steered, steered.ok && textEquals(okText(steered), "WORK_EXPANSION_STEER_OK"));
|
|
216
|
+
const workAgent = chatgpt.agent({
|
|
217
|
+
name: "live-smoke-work-runner",
|
|
218
|
+
defaults: {
|
|
219
|
+
wait: { timeoutMs: 180000, stableMs: 1500, pollMs: 750 },
|
|
220
|
+
read: { format: "normalized_text" }
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
const runner = await chatgpt.runner.run(workAgent, {
|
|
224
|
+
input: "Reply with exactly WORK_EXPANSION_RUNNER_OK.",
|
|
225
|
+
thread: { type: "current" },
|
|
226
|
+
experience: "work",
|
|
227
|
+
configuration: workDesired,
|
|
228
|
+
response: { format: "normalized_text" }
|
|
229
|
+
});
|
|
230
|
+
terminal = asCommand(runner);
|
|
231
|
+
requireLiveCommand("runner.run.work", runner, runner.ok && textEquals(runner.output_text, "WORK_EXPANSION_RUNNER_OK"));
|
|
232
|
+
const response = await chatgpt.responses.create({
|
|
233
|
+
input: "Reply with exactly WORK_EXPANSION_RESPONSES_OK.",
|
|
234
|
+
thread: { type: "current" },
|
|
235
|
+
experience: "work",
|
|
236
|
+
configuration: workDesired,
|
|
237
|
+
text: { format: "normalized_text" },
|
|
238
|
+
stream: false
|
|
239
|
+
});
|
|
240
|
+
const responseResult = responseCommand(response);
|
|
241
|
+
terminal = responseResult;
|
|
242
|
+
requireLiveCommand("responses.create.work", responseResult, response.object === "chatgpt.browser.response"
|
|
243
|
+
&& response.status === "ok"
|
|
244
|
+
&& textEquals(response.output_text, "WORK_EXPANSION_RESPONSES_OK"));
|
|
245
|
+
details.chat = configurationDetails(chatConfiguration.data);
|
|
246
|
+
details.work = configurationDetails(workConfiguration.data);
|
|
247
|
+
details.workLifecycle = {
|
|
248
|
+
start: true,
|
|
249
|
+
status: true,
|
|
250
|
+
wait: true,
|
|
251
|
+
read: true,
|
|
252
|
+
steer: true,
|
|
253
|
+
artifacts: true,
|
|
254
|
+
runner: true,
|
|
255
|
+
responses: true
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (error instanceof LiveSmokeCommandFailure) {
|
|
260
|
+
failure = error;
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
if (booted) {
|
|
268
|
+
const restored = await chatgpt.experience.open({ experience: "chat", timeoutMs: 60000 });
|
|
269
|
+
terminal = asCommand(restored);
|
|
270
|
+
if (!restored.ok || restored.data?.experience !== "chat") {
|
|
271
|
+
failure = new LiveSmokeCommandFailure("experience.restore.chat", asCommand(restored));
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
details.restoredExperience = "chat";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return failure === undefined
|
|
279
|
+
? pass(meta, terminal, details)
|
|
280
|
+
: fail(meta, failure.command, { ...details, failedStage: failure.stage });
|
|
281
|
+
}),
|
|
125
282
|
scenario("redacted-run-report", true, () => true, async (context, meta) => {
|
|
126
283
|
const chatgpt = createChatGPT(clientOptionsFor(context));
|
|
127
284
|
const command = {
|
|
@@ -448,6 +605,84 @@ export const requiredScenarios = [
|
|
|
448
605
|
})
|
|
449
606
|
];
|
|
450
607
|
export const optionalScenarios = [
|
|
608
|
+
scenario("configuration-mutate-restore", false, context => contextEnvFlag(context, "CHATGPT_E2E_CONFIGURATION_MUTATION"), async (context, meta) => {
|
|
609
|
+
const chatgpt = createChatGPT(clientOptionsFor(context));
|
|
610
|
+
const details = {};
|
|
611
|
+
let booted = false;
|
|
612
|
+
let restoreNeeded = false;
|
|
613
|
+
let originalEffort;
|
|
614
|
+
let terminal = syntheticCommand(meta.startedAt);
|
|
615
|
+
let failure;
|
|
616
|
+
try {
|
|
617
|
+
const boot = await chatgpt.session.bootstrap({ preferExistingTab: false, timeoutMs: 60000 });
|
|
618
|
+
terminal = asCommand(boot);
|
|
619
|
+
requireLiveCommand("session.bootstrap", boot);
|
|
620
|
+
booted = true;
|
|
621
|
+
const opened = await chatgpt.experience.open({ experience: "work", timeoutMs: 60000 });
|
|
622
|
+
terminal = asCommand(opened);
|
|
623
|
+
requireLiveCommand("experience.open.work", opened, opened.ok && opened.data?.experience === "work");
|
|
624
|
+
const inspected = await chatgpt.configuration.inspect({
|
|
625
|
+
experience: "work",
|
|
626
|
+
includeOptions: true,
|
|
627
|
+
timeoutMs: 60000
|
|
628
|
+
});
|
|
629
|
+
terminal = asCommand(inspected);
|
|
630
|
+
requireLiveCommand("configuration.inspect.work", inspected, inspected.ok && inspected.data?.verified === true);
|
|
631
|
+
originalEffort = inspected.data?.active.effort;
|
|
632
|
+
const alternative = inspected.data?.options.effort?.find(option => !option.selected && option.disabled !== true)?.label;
|
|
633
|
+
requireLiveCommand("configuration.inspect.work.alternative", inspected, originalEffort !== undefined && alternative !== undefined);
|
|
634
|
+
restoreNeeded = true;
|
|
635
|
+
const changed = await chatgpt.configuration.apply({
|
|
636
|
+
experience: "work",
|
|
637
|
+
desired: { effort: alternative },
|
|
638
|
+
strict: true,
|
|
639
|
+
timeoutMs: 60000
|
|
640
|
+
});
|
|
641
|
+
terminal = asCommand(changed);
|
|
642
|
+
requireLiveCommand("configuration.apply.work.mutate", changed, changed.ok && changed.data?.verified === true);
|
|
643
|
+
details.axis = "effort";
|
|
644
|
+
details.changedFrom = originalEffort;
|
|
645
|
+
details.changedTo = alternative;
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
if (error instanceof LiveSmokeCommandFailure) {
|
|
649
|
+
failure = error;
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
throw error;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
finally {
|
|
656
|
+
if (booted && restoreNeeded && originalEffort !== undefined) {
|
|
657
|
+
const restoredConfiguration = await chatgpt.configuration.apply({
|
|
658
|
+
experience: "work",
|
|
659
|
+
desired: { effort: originalEffort },
|
|
660
|
+
strict: true,
|
|
661
|
+
timeoutMs: 60000
|
|
662
|
+
});
|
|
663
|
+
terminal = asCommand(restoredConfiguration);
|
|
664
|
+
if (!restoredConfiguration.ok || restoredConfiguration.data?.verified !== true) {
|
|
665
|
+
failure = new LiveSmokeCommandFailure("configuration.restore.work", asCommand(restoredConfiguration));
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
details.configurationRestored = true;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (booted) {
|
|
672
|
+
const restoredExperience = await chatgpt.experience.open({ experience: "chat", timeoutMs: 60000 });
|
|
673
|
+
terminal = asCommand(restoredExperience);
|
|
674
|
+
if (!restoredExperience.ok || restoredExperience.data?.experience !== "chat") {
|
|
675
|
+
failure = new LiveSmokeCommandFailure("experience.restore.chat", asCommand(restoredExperience));
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
details.restoredExperience = "chat";
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return failure === undefined
|
|
683
|
+
? pass(meta, terminal, details)
|
|
684
|
+
: fail(meta, failure.command, { ...details, failedStage: failure.stage });
|
|
685
|
+
}),
|
|
451
686
|
scenario("long-response-partial-short-timeout", false, context => contextEnvFlag(context, "CHATGPT_E2E_LONG_PARTIAL"), async (context, meta) => {
|
|
452
687
|
const env = await bootNewThread(context, meta);
|
|
453
688
|
if ("status" in env)
|
|
@@ -533,17 +768,35 @@ export const optionalScenarios = [
|
|
|
533
768
|
const env = await bootNewThread(context, meta);
|
|
534
769
|
if ("status" in env)
|
|
535
770
|
return env;
|
|
771
|
+
const chat = await openExperience(env, { experience: "chat", timeoutMs: 60000 });
|
|
772
|
+
if (!chat.ok || chat.data?.experience !== "chat") {
|
|
773
|
+
return fail(meta, chat, { failedStage: "experience.open.chat" });
|
|
774
|
+
}
|
|
536
775
|
const asked = await askMessage(env, {
|
|
537
776
|
text: "Create a tiny CSV file named chatgpt-live-smoke.csv containing one row with columns name,value and values smoke,1. Provide it as a downloadable file.",
|
|
538
777
|
wait: { timeoutMs: 180000, stableMs: 3000 },
|
|
539
778
|
read: true
|
|
540
779
|
});
|
|
541
|
-
if (!asked
|
|
780
|
+
if (!generatedFileAskCanProceed(asked))
|
|
542
781
|
return fail(meta, asked);
|
|
543
|
-
const result = await downloadLatestAttachment({
|
|
544
|
-
|
|
782
|
+
const result = await downloadLatestAttachment({
|
|
783
|
+
destDir: context.reportDir,
|
|
784
|
+
filenamePattern: "^chatgpt-live-smoke\\.csv$",
|
|
785
|
+
timeoutMs: 120000
|
|
786
|
+
}, env);
|
|
787
|
+
const download = typeof result.data === "object" && result.data !== null
|
|
788
|
+
? result.data
|
|
789
|
+
: undefined;
|
|
790
|
+
const path = download?.path;
|
|
545
791
|
const bytes = path === undefined ? 0 : (await stat(path).catch(() => undefined))?.size ?? 0;
|
|
546
|
-
|
|
792
|
+
const content = path === undefined ? "" : await readFile(path, "utf8").catch(() => "");
|
|
793
|
+
const rows = content.replace(/^\uFEFF/, "").trim().split(/\r?\n/).map(row => row.trim());
|
|
794
|
+
const exactFile = download?.suggestedFilename === "chatgpt-live-smoke.csv";
|
|
795
|
+
const exactContent = rows[0] === "name,value" && rows[1] === "smoke,1" && rows.length === 2;
|
|
796
|
+
const details = { path, bytes, suggestedFilename: download?.suggestedFilename, exactFile, exactContent, askStatus: asked.status };
|
|
797
|
+
return result.ok && bytes > 0 && exactFile && exactContent
|
|
798
|
+
? pass(meta, result, details)
|
|
799
|
+
: fail(meta, result, details);
|
|
547
800
|
}),
|
|
548
801
|
scenario("set-mode-visible", false, context => contextEnvText(context, "CHATGPT_E2E_MODE_LABEL") !== undefined, async (context, meta) => {
|
|
549
802
|
const label = requireInput(contextEnvText(context, "CHATGPT_E2E_MODE_LABEL"), "CHATGPT_E2E_MODE_LABEL");
|
|
@@ -606,6 +859,10 @@ export const optionalScenarios = [
|
|
|
606
859
|
: fail(meta, result, { events });
|
|
607
860
|
})
|
|
608
861
|
];
|
|
862
|
+
export function generatedFileAskCanProceed(result) {
|
|
863
|
+
return result.ok
|
|
864
|
+
|| (result.status === "partial" && result.data?.generationActive !== true);
|
|
865
|
+
}
|
|
609
866
|
function scenario(name, required, enabled, run) {
|
|
610
867
|
return {
|
|
611
868
|
name,
|
|
@@ -722,6 +979,61 @@ function responseCommand(response) {
|
|
|
722
979
|
context: { timestamp: new Date(response.created_at * 1000).toISOString() }
|
|
723
980
|
};
|
|
724
981
|
}
|
|
982
|
+
class LiveSmokeCommandFailure extends Error {
|
|
983
|
+
stage;
|
|
984
|
+
command;
|
|
985
|
+
constructor(stage, command) {
|
|
986
|
+
super(`Live smoke command failed at ${stage}`);
|
|
987
|
+
this.stage = stage;
|
|
988
|
+
this.command = command;
|
|
989
|
+
this.name = "LiveSmokeCommandFailure";
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function requireLiveCommand(stage, command, condition = command.ok) {
|
|
993
|
+
if (!condition) {
|
|
994
|
+
throw new LiveSmokeCommandFailure(stage, asCommand(command));
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function asCommand(command) {
|
|
998
|
+
return command;
|
|
999
|
+
}
|
|
1000
|
+
function syntheticCommand(timestamp) {
|
|
1001
|
+
return {
|
|
1002
|
+
ok: true,
|
|
1003
|
+
status: "ok",
|
|
1004
|
+
warnings: [],
|
|
1005
|
+
context: { timestamp }
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function activeSelection(inspection, axes) {
|
|
1009
|
+
const selection = {};
|
|
1010
|
+
for (const axis of axes) {
|
|
1011
|
+
const value = inspection.active[axis];
|
|
1012
|
+
if (value !== undefined) {
|
|
1013
|
+
selection[axis] = value;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return selection;
|
|
1017
|
+
}
|
|
1018
|
+
export function chatActiveSelection(inspection) {
|
|
1019
|
+
return activeSelection(inspection, ["intelligence", "model", "modelVersion", "effort"]);
|
|
1020
|
+
}
|
|
1021
|
+
function hasAxes(inspection, axes) {
|
|
1022
|
+
return axes.every(axis => inspection.availableAxes.includes(axis));
|
|
1023
|
+
}
|
|
1024
|
+
function hasSelectionAxes(selection, axes) {
|
|
1025
|
+
return axes.every(axis => selection[axis] !== undefined);
|
|
1026
|
+
}
|
|
1027
|
+
function configurationDetails(inspection) {
|
|
1028
|
+
return {
|
|
1029
|
+
experience: inspection.experience,
|
|
1030
|
+
selectorProfile: inspection.selectorProfile,
|
|
1031
|
+
verified: inspection.verified,
|
|
1032
|
+
availableAxes: inspection.availableAxes,
|
|
1033
|
+
active: inspection.active,
|
|
1034
|
+
optionCounts: Object.fromEntries(Object.entries(inspection.options).map(([axis, options]) => [axis, options?.length ?? 0]))
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
725
1037
|
function partialCaptureDetails(result, output) {
|
|
726
1038
|
return {
|
|
727
1039
|
resultStatus: result.status,
|
|
@@ -734,21 +1046,17 @@ async function waitForGenerationSignal(env, timeoutMs) {
|
|
|
734
1046
|
const started = Date.now();
|
|
735
1047
|
while (Date.now() - started < timeoutMs) {
|
|
736
1048
|
if (env.page !== undefined) {
|
|
737
|
-
const state = await readAssistantGenerationState(env.page).catch(() =>
|
|
1049
|
+
const state = await readAssistantGenerationState(env.page).catch(() => EMPTY_GENERATION_STATE);
|
|
738
1050
|
if (state.active || state.stopped) {
|
|
739
1051
|
return state;
|
|
740
1052
|
}
|
|
741
1053
|
}
|
|
742
1054
|
await env.page?.waitForTimeout?.(500);
|
|
743
1055
|
}
|
|
744
|
-
return
|
|
1056
|
+
return EMPTY_GENERATION_STATE;
|
|
745
1057
|
}
|
|
746
1058
|
async function stopGenerationIfVisible(env) {
|
|
747
|
-
|
|
748
|
-
const clicked = stop?.click?.({ timeoutMs: 5000 });
|
|
749
|
-
if (clicked !== undefined) {
|
|
750
|
-
await clicked.catch(() => undefined);
|
|
751
|
-
}
|
|
1059
|
+
await stopGeneration(env, { confirmStop: true, timeoutMs: 5_000 }).catch(() => undefined);
|
|
752
1060
|
}
|
|
753
1061
|
function hashPreview(text) {
|
|
754
1062
|
let hash = 0;
|
|
@@ -39,6 +39,7 @@ else {
|
|
|
39
39
|
context.knownConversationId = knownConversationId;
|
|
40
40
|
}
|
|
41
41
|
context.env = {
|
|
42
|
+
CHATGPT_E2E_CONFIGURATION_MUTATION: envText("CHATGPT_E2E_CONFIGURATION_MUTATION"),
|
|
42
43
|
CHATGPT_E2E_CREATE_IMAGE: envText("CHATGPT_E2E_CREATE_IMAGE"),
|
|
43
44
|
CHATGPT_E2E_DEEP_RESEARCH: envText("CHATGPT_E2E_DEEP_RESEARCH"),
|
|
44
45
|
CHATGPT_E2E_DOWNLOAD: envText("CHATGPT_E2E_DOWNLOAD"),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type RawSurfaceOption = {
|
|
2
|
+
label: string;
|
|
3
|
+
checked: boolean;
|
|
4
|
+
};
|
|
5
|
+
export type RawConfigurationRow = {
|
|
6
|
+
label: string;
|
|
7
|
+
axisLabel: string;
|
|
8
|
+
valueLabel?: string;
|
|
9
|
+
options: Array<{
|
|
10
|
+
label: string;
|
|
11
|
+
checked: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
export type RawWorkConfigurationRow = RawConfigurationRow;
|
|
15
|
+
export type CapturedChatConfigurationRow = RawConfigurationRow & {
|
|
16
|
+
axis: "model" | "effort";
|
|
17
|
+
};
|
|
18
|
+
export type CapturedWorkConfigurationRow = RawConfigurationRow & {
|
|
19
|
+
axis: "model" | "effort" | "speed";
|
|
20
|
+
};
|
|
21
|
+
export declare function assignChatSelectedSurfaceOptions(options: readonly RawSurfaceOption[]): {
|
|
22
|
+
chatLabel: string;
|
|
23
|
+
workLabel: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function assignOrderedSurfaceOptions(options: readonly RawSurfaceOption[]): {
|
|
26
|
+
chatLabel: string;
|
|
27
|
+
workLabel: string;
|
|
28
|
+
selected: "chat" | "work";
|
|
29
|
+
};
|
|
30
|
+
export declare function assignOrderedWorkConfigurationRows(rows: readonly RawWorkConfigurationRow[]): CapturedWorkConfigurationRow[];
|
|
31
|
+
export declare function assignOrderedChatConfigurationRows(rows: readonly RawConfigurationRow[]): CapturedChatConfigurationRow[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function assignChatSelectedSurfaceOptions(options) {
|
|
2
|
+
const unique = dedupeByLabel(options.filter(option => option.label.trim().length > 0));
|
|
3
|
+
const selected = unique.filter(option => option.checked);
|
|
4
|
+
const unselected = unique.filter(option => !option.checked);
|
|
5
|
+
if (unique.length !== 2 || selected.length !== 1 || unselected.length !== 1) {
|
|
6
|
+
throw new Error(`Expected one selected Chat radio and one unselected Work radio; observed ${unique.length} options.`);
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
chatLabel: selected[0].label,
|
|
10
|
+
workLabel: unselected[0].label
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function assignOrderedSurfaceOptions(options) {
|
|
14
|
+
const unique = dedupeByLabel(options.filter(option => option.label.trim().length > 0));
|
|
15
|
+
const selected = unique.filter(option => option.checked);
|
|
16
|
+
if (unique.length !== 2 || selected.length !== 1) {
|
|
17
|
+
throw new Error(`Expected ordered Chat and Work radios with one selected option; observed ${unique.length} options.`);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
chatLabel: unique[0].label,
|
|
21
|
+
workLabel: unique[1].label,
|
|
22
|
+
selected: unique[0].checked ? "chat" : "work"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function assignOrderedWorkConfigurationRows(rows) {
|
|
26
|
+
if (rows.length !== 3) {
|
|
27
|
+
throw new Error(`Expected three ordered Work configuration rows; observed ${rows.length}.`);
|
|
28
|
+
}
|
|
29
|
+
const axes = ["model", "effort", "speed"];
|
|
30
|
+
return rows.map((row, index) => ({ ...row, axis: axes[index] }));
|
|
31
|
+
}
|
|
32
|
+
export function assignOrderedChatConfigurationRows(rows) {
|
|
33
|
+
if (rows.length !== 2) {
|
|
34
|
+
throw new Error(`Expected two ordered Chat configuration rows; observed ${rows.length}.`);
|
|
35
|
+
}
|
|
36
|
+
const axes = ["model", "effort"];
|
|
37
|
+
return rows.map((row, index) => ({ ...row, axis: axes[index] }));
|
|
38
|
+
}
|
|
39
|
+
function dedupeByLabel(options) {
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const result = [];
|
|
42
|
+
for (const option of options) {
|
|
43
|
+
const key = option.label.replace(/\s+/g, " ").trim().toLocaleLowerCase();
|
|
44
|
+
if (seen.has(key))
|
|
45
|
+
continue;
|
|
46
|
+
seen.add(key);
|
|
47
|
+
result.push({ label: option.label.replace(/\s+/g, " ").trim(), checked: option.checked });
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|