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.
Files changed (233) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +42 -2
  3. package/contracts/v1/fixtures/backend-capabilities.json +11 -0
  4. package/contracts/v1/fixtures/command-descriptors.json +289 -7
  5. package/contracts/v1/fixtures/describe-runner-run.json +5 -2
  6. package/contracts/v1/fixtures/doctor-scenario-preflight.json +45 -2
  7. package/contracts/v1/fixtures/help-root.json +1 -1
  8. package/contracts/v1/fixtures/output-json-parse-success.json +9 -0
  9. package/contracts/v1/fixtures/reports-create-redacted.json +1 -1
  10. package/contracts/v1/fixtures/run-timeout-partial.json +15 -4
  11. package/contracts/v1/fixtures/stream-in-progress.ndjson +3 -0
  12. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
  13. package/contracts/v1/fixtures/surface-chat-legacy.json +35 -0
  14. package/contracts/v1/fixtures/surface-chat-simplified.json +37 -0
  15. package/contracts/v1/fixtures/surface-sidebar-false-positive.json +29 -0
  16. package/contracts/v1/fixtures/surface-work-advanced.json +45 -0
  17. package/contracts/v1/fixtures/surface-work-basic.json +38 -0
  18. package/contracts/v1/fixtures/workflow-ask-success.json +8 -0
  19. package/contracts/v1/manifest.json +32 -1
  20. package/contracts/v1/parity-suite.json +121 -1
  21. package/contracts/v1/schemas/backend-request.schema.json +15 -0
  22. package/contracts/v1/schemas/manifest.schema.json +6 -3
  23. package/contracts/v1/schemas/surface-profile.schema.json +166 -0
  24. package/dist/codex-chatgpt-control-backend.mjs +3198 -340
  25. package/dist/codex-chatgpt-control.bundle.mjs +4454 -1555
  26. package/dist/src/backend/client.d.ts +26 -1
  27. package/dist/src/backend/client.js +23 -1
  28. package/dist/src/backend/protocol.d.ts +1 -1
  29. package/dist/src/backend/protocol.js +11 -0
  30. package/dist/src/backend/session.js +22 -0
  31. package/dist/src/browser/attach.d.ts +1 -0
  32. package/dist/src/browser/attach.js +3 -3
  33. package/dist/src/browser/clipboard.d.ts +11 -0
  34. package/dist/src/browser/clipboard.js +34 -7
  35. package/dist/src/browser/page-state.js +17 -4
  36. package/dist/src/client.d.ts +32 -1
  37. package/dist/src/client.js +72 -16
  38. package/dist/src/commands/artifacts.js +1 -7
  39. package/dist/src/commands/configuration.d.ts +15 -0
  40. package/dist/src/commands/configuration.js +674 -0
  41. package/dist/src/commands/context.js +3 -1
  42. package/dist/src/commands/conversation.d.ts +15 -0
  43. package/dist/src/commands/conversation.js +44 -0
  44. package/dist/src/commands/deadline.d.ts +8 -0
  45. package/dist/src/commands/deadline.js +14 -0
  46. package/dist/src/commands/doctor.js +47 -3
  47. package/dist/src/commands/experience.d.ts +12 -0
  48. package/dist/src/commands/experience.js +288 -0
  49. package/dist/src/commands/files.js +61 -13
  50. package/dist/src/commands/messages.d.ts +2 -1
  51. package/dist/src/commands/messages.js +349 -87
  52. package/dist/src/commands/modes.d.ts +4 -1
  53. package/dist/src/commands/modes.js +243 -44
  54. package/dist/src/commands/probes.d.ts +15 -0
  55. package/dist/src/commands/probes.js +64 -0
  56. package/dist/src/commands/registry.js +102 -8
  57. package/dist/src/commands/reports.js +1 -1
  58. package/dist/src/commands/response-actions.js +1 -7
  59. package/dist/src/commands/sequence.js +24 -1
  60. package/dist/src/commands/session.d.ts +2 -0
  61. package/dist/src/commands/session.js +50 -1
  62. package/dist/src/commands/threads.js +3 -31
  63. package/dist/src/commands/work.d.ts +6 -0
  64. package/dist/src/commands/work.js +338 -0
  65. package/dist/src/dom/generation-state.d.ts +7 -0
  66. package/dist/src/dom/generation-state.js +7 -1
  67. package/dist/src/dom/label-match.d.ts +3 -0
  68. package/dist/src/dom/label-match.js +25 -0
  69. package/dist/src/dom/locale/am.d.ts +8 -1
  70. package/dist/src/dom/locale/am.js +8 -1
  71. package/dist/src/dom/locale/ar.d.ts +9 -1
  72. package/dist/src/dom/locale/ar.js +9 -1
  73. package/dist/src/dom/locale/bg.d.ts +9 -1
  74. package/dist/src/dom/locale/bg.js +9 -1
  75. package/dist/src/dom/locale/bn.d.ts +9 -1
  76. package/dist/src/dom/locale/bn.js +9 -1
  77. package/dist/src/dom/locale/bs.d.ts +8 -1
  78. package/dist/src/dom/locale/bs.js +8 -1
  79. package/dist/src/dom/locale/ca.d.ts +8 -1
  80. package/dist/src/dom/locale/ca.js +8 -1
  81. package/dist/src/dom/locale/cs.d.ts +8 -1
  82. package/dist/src/dom/locale/cs.js +8 -1
  83. package/dist/src/dom/locale/da.d.ts +7 -1
  84. package/dist/src/dom/locale/da.js +7 -1
  85. package/dist/src/dom/locale/de.d.ts +10 -3
  86. package/dist/src/dom/locale/de.js +10 -3
  87. package/dist/src/dom/locale/el.d.ts +8 -1
  88. package/dist/src/dom/locale/el.js +8 -1
  89. package/dist/src/dom/locale/en.d.ts +40 -1
  90. package/dist/src/dom/locale/en.js +42 -1
  91. package/dist/src/dom/locale/es-419.d.ts +8 -1
  92. package/dist/src/dom/locale/es-419.js +8 -1
  93. package/dist/src/dom/locale/es-ES.d.ts +8 -1
  94. package/dist/src/dom/locale/es-ES.js +8 -1
  95. package/dist/src/dom/locale/et.d.ts +8 -1
  96. package/dist/src/dom/locale/et.js +8 -1
  97. package/dist/src/dom/locale/fa.d.ts +9 -1
  98. package/dist/src/dom/locale/fa.js +9 -1
  99. package/dist/src/dom/locale/fi.d.ts +8 -1
  100. package/dist/src/dom/locale/fi.js +8 -1
  101. package/dist/src/dom/locale/fr-CA.d.ts +8 -1
  102. package/dist/src/dom/locale/fr-CA.js +8 -1
  103. package/dist/src/dom/locale/fr-FR.d.ts +8 -3
  104. package/dist/src/dom/locale/fr-FR.js +8 -3
  105. package/dist/src/dom/locale/gu.d.ts +8 -1
  106. package/dist/src/dom/locale/gu.js +8 -1
  107. package/dist/src/dom/locale/hi.d.ts +8 -1
  108. package/dist/src/dom/locale/hi.js +8 -1
  109. package/dist/src/dom/locale/hr.d.ts +7 -1
  110. package/dist/src/dom/locale/hr.js +7 -1
  111. package/dist/src/dom/locale/hu.d.ts +8 -1
  112. package/dist/src/dom/locale/hu.js +8 -1
  113. package/dist/src/dom/locale/hy.d.ts +9 -1
  114. package/dist/src/dom/locale/hy.js +9 -1
  115. package/dist/src/dom/locale/id.d.ts +8 -1
  116. package/dist/src/dom/locale/id.js +8 -1
  117. package/dist/src/dom/locale/index.d.ts +20 -1
  118. package/dist/src/dom/locale/index.js +102 -0
  119. package/dist/src/dom/locale/is.d.ts +8 -1
  120. package/dist/src/dom/locale/is.js +8 -1
  121. package/dist/src/dom/locale/it.d.ts +8 -1
  122. package/dist/src/dom/locale/it.js +8 -1
  123. package/dist/src/dom/locale/ja.d.ts +8 -1
  124. package/dist/src/dom/locale/ja.js +8 -1
  125. package/dist/src/dom/locale/ka.d.ts +8 -1
  126. package/dist/src/dom/locale/ka.js +8 -1
  127. package/dist/src/dom/locale/kk.d.ts +8 -1
  128. package/dist/src/dom/locale/kk.js +8 -1
  129. package/dist/src/dom/locale/kn.d.ts +9 -1
  130. package/dist/src/dom/locale/kn.js +9 -1
  131. package/dist/src/dom/locale/ko.d.ts +8 -1
  132. package/dist/src/dom/locale/ko.js +8 -1
  133. package/dist/src/dom/locale/lt.d.ts +9 -1
  134. package/dist/src/dom/locale/lt.js +9 -1
  135. package/dist/src/dom/locale/lv.d.ts +8 -1
  136. package/dist/src/dom/locale/lv.js +8 -1
  137. package/dist/src/dom/locale/mk.d.ts +7 -1
  138. package/dist/src/dom/locale/mk.js +7 -1
  139. package/dist/src/dom/locale/ml.d.ts +9 -1
  140. package/dist/src/dom/locale/ml.js +9 -1
  141. package/dist/src/dom/locale/mn.d.ts +9 -1
  142. package/dist/src/dom/locale/mn.js +9 -1
  143. package/dist/src/dom/locale/mr.d.ts +9 -1
  144. package/dist/src/dom/locale/mr.js +9 -1
  145. package/dist/src/dom/locale/ms.d.ts +8 -1
  146. package/dist/src/dom/locale/ms.js +8 -1
  147. package/dist/src/dom/locale/my.d.ts +8 -1
  148. package/dist/src/dom/locale/my.js +8 -1
  149. package/dist/src/dom/locale/nb.d.ts +8 -1
  150. package/dist/src/dom/locale/nb.js +8 -1
  151. package/dist/src/dom/locale/nl.d.ts +8 -1
  152. package/dist/src/dom/locale/nl.js +8 -1
  153. package/dist/src/dom/locale/pa.d.ts +9 -1
  154. package/dist/src/dom/locale/pa.js +9 -1
  155. package/dist/src/dom/locale/pl.d.ts +8 -1
  156. package/dist/src/dom/locale/pl.js +8 -1
  157. package/dist/src/dom/locale/pt-BR.d.ts +8 -1
  158. package/dist/src/dom/locale/pt-BR.js +8 -1
  159. package/dist/src/dom/locale/pt-PT.d.ts +8 -1
  160. package/dist/src/dom/locale/pt-PT.js +8 -1
  161. package/dist/src/dom/locale/ro.d.ts +7 -1
  162. package/dist/src/dom/locale/ro.js +7 -1
  163. package/dist/src/dom/locale/ru.d.ts +5 -1
  164. package/dist/src/dom/locale/ru.js +5 -1
  165. package/dist/src/dom/locale/sk.d.ts +8 -1
  166. package/dist/src/dom/locale/sk.js +8 -1
  167. package/dist/src/dom/locale/sl.d.ts +8 -1
  168. package/dist/src/dom/locale/sl.js +8 -1
  169. package/dist/src/dom/locale/so.d.ts +8 -1
  170. package/dist/src/dom/locale/so.js +8 -1
  171. package/dist/src/dom/locale/sq.d.ts +8 -1
  172. package/dist/src/dom/locale/sq.js +8 -1
  173. package/dist/src/dom/locale/sr.d.ts +5 -1
  174. package/dist/src/dom/locale/sr.js +5 -1
  175. package/dist/src/dom/locale/sv.d.ts +8 -1
  176. package/dist/src/dom/locale/sv.js +8 -1
  177. package/dist/src/dom/locale/sw.d.ts +8 -1
  178. package/dist/src/dom/locale/sw.js +8 -1
  179. package/dist/src/dom/locale/ta.d.ts +9 -1
  180. package/dist/src/dom/locale/ta.js +9 -1
  181. package/dist/src/dom/locale/te.d.ts +9 -1
  182. package/dist/src/dom/locale/te.js +9 -1
  183. package/dist/src/dom/locale/th.d.ts +8 -1
  184. package/dist/src/dom/locale/th.js +8 -1
  185. package/dist/src/dom/locale/tl.d.ts +4 -5
  186. package/dist/src/dom/locale/tl.js +4 -5
  187. package/dist/src/dom/locale/tr.d.ts +8 -1
  188. package/dist/src/dom/locale/tr.js +8 -1
  189. package/dist/src/dom/locale/types.d.ts +25 -1
  190. package/dist/src/dom/locale/uk.d.ts +8 -1
  191. package/dist/src/dom/locale/uk.js +8 -1
  192. package/dist/src/dom/locale/ur.d.ts +8 -1
  193. package/dist/src/dom/locale/ur.js +8 -1
  194. package/dist/src/dom/locale/vi.d.ts +8 -1
  195. package/dist/src/dom/locale/vi.js +8 -1
  196. package/dist/src/dom/locale/zh-HK.d.ts +9 -1
  197. package/dist/src/dom/locale/zh-HK.js +10 -2
  198. package/dist/src/dom/locale/zh-Hans.d.ts +9 -1
  199. package/dist/src/dom/locale/zh-Hans.js +9 -1
  200. package/dist/src/dom/locale/zh-TW.d.ts +9 -1
  201. package/dist/src/dom/locale/zh-TW.js +9 -1
  202. package/dist/src/dom/menus.js +13 -2
  203. package/dist/src/dom/selectors.js +6 -1
  204. package/dist/src/dom/wait-snapshot.d.ts +43 -0
  205. package/dist/src/dom/wait-snapshot.js +127 -0
  206. package/dist/src/index.d.ts +3 -0
  207. package/dist/src/index.js +3 -0
  208. package/dist/src/runner/responses.d.ts +3 -1
  209. package/dist/src/runner/responses.js +16 -1
  210. package/dist/src/runner/result.js +129 -4
  211. package/dist/src/runner/stream.d.ts +1 -1
  212. package/dist/src/runner/stream.js +6 -0
  213. package/dist/src/runner/types.d.ts +35 -1
  214. package/dist/src/safety/risk.d.ts +11 -0
  215. package/dist/src/safety/risk.js +11 -0
  216. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +6 -0
  217. package/dist/src/scripts/apply-intelligence-locale-captures.js +147 -15
  218. package/dist/src/scripts/capture-intelligence-locales.d.ts +3 -0
  219. package/dist/src/scripts/capture-intelligence-locales.js +372 -3
  220. package/dist/src/scripts/capture-surface-profile.d.ts +23 -0
  221. package/dist/src/scripts/capture-surface-profile.js +304 -0
  222. package/dist/src/scripts/live-smoke/scenarios.js +34 -1
  223. package/dist/src/scripts/live-smoke.js +1 -0
  224. package/dist/src/types.d.ts +273 -2
  225. package/package.json +4 -3
  226. package/references/2026-07-16-chat-work-surfaces.md +87 -0
  227. package/references/agents-runner.md +13 -2
  228. package/references/backend-protocol.md +19 -7
  229. package/references/language-coverage.md +40 -3
  230. package/references/localization.md +48 -10
  231. package/references/python-parity.md +16 -4
  232. package/references/responses-adapter.md +12 -1
  233. package/references/troubleshooting.md +49 -6
@@ -0,0 +1,304 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { attachChatGPTBrowser } from "../browser/attach.js";
4
+ import { inspectConfiguration } from "../commands/configuration.js";
5
+ import { detectExperienceFromSnapshot, readSurfaceSnapshot } from "../commands/experience.js";
6
+ import { BROWSER_BRIDGE_REMEDIATION, BROWSER_BRIDGE_UNAVAILABLE_MESSAGE, toCommandResult } from "../errors.js";
7
+ const CHATGPT_HOME = "https://chatgpt.com/";
8
+ const DEFAULT_PROVENANCE = "Read-only sanitized capture from visible ChatGPT controls; review before committing.";
9
+ class UsageError extends Error {
10
+ exitCode;
11
+ constructor(message, exitCode = 2) {
12
+ super(message);
13
+ this.exitCode = exitCode;
14
+ this.name = "UsageError";
15
+ }
16
+ }
17
+ const USAGE = [
18
+ "Usage:",
19
+ " npm run capture:surface-profile -- --id work-basic-en",
20
+ " npm run capture:surface-profile -- --id chat-simplified-de --locale de-DE --out ../../outputs/surface-profiles/chat-simplified-de.json",
21
+ "",
22
+ "Options:",
23
+ " --id Required normalized profile id.",
24
+ " --out Draft JSON path. Defaults under ../../outputs/surface-profiles/.",
25
+ " --locale Known BCP47 locale; otherwise reads documentElement.lang.",
26
+ " --region Normalized supplied region group. Default: not-recorded.",
27
+ " --account-scope Normalized supplied account group. Default: not-recorded.",
28
+ " --plan-scope Normalized supplied plan group. Default: not-recorded.",
29
+ " --workspace-scope Normalized supplied workspace group. Default: not-recorded.",
30
+ " --support-state current|compatibility|unverified|retired. Default: unverified.",
31
+ " --provenance Non-private provenance note.",
32
+ " --tab-id Claim an exact already-open ChatGPT tab.",
33
+ " --if-missing block|open|create. Default: block.",
34
+ "",
35
+ "The capture is read-only with respect to ChatGPT configuration. It opens and",
36
+ "closes visible menus, writes a sanitized draft locally, and never records",
37
+ "prompts, responses, sidebar titles, account names, cookies, or network data."
38
+ ].join("\n");
39
+ export async function main(argv = process.argv.slice(2), runtime = globalThis) {
40
+ let options;
41
+ try {
42
+ options = parseArgs(argv);
43
+ }
44
+ catch (error) {
45
+ if (error instanceof UsageError) {
46
+ console.log(error.message);
47
+ return error.exitCode;
48
+ }
49
+ throw error;
50
+ }
51
+ if ((runtime.agent === undefined || runtime.agent === null) && runtime.browser === undefined) {
52
+ console.log(JSON.stringify({
53
+ ok: false,
54
+ status: "blocked",
55
+ blocker: {
56
+ kind: "browser_bridge_unavailable",
57
+ code: "codex_chrome_bridge_unavailable",
58
+ message: BROWSER_BRIDGE_UNAVAILABLE_MESSAGE,
59
+ remediation: BROWSER_BRIDGE_REMEDIATION
60
+ }
61
+ }, null, 2));
62
+ return 2;
63
+ }
64
+ try {
65
+ const env = {};
66
+ if (runtime.agent !== undefined && runtime.agent !== null)
67
+ env.agent = runtime.agent;
68
+ if (runtime.browser !== undefined)
69
+ env.browser = runtime.browser;
70
+ const attached = await attachChatGPTBrowser(env, {
71
+ existingTab: {
72
+ target: options.tabId === undefined
73
+ ? { type: "selected", host: "chatgpt" }
74
+ : { type: "tabId", tabId: options.tabId },
75
+ ifMissing: options.ifMissing,
76
+ ifMultiple: "first",
77
+ requireChatGPT: true
78
+ },
79
+ url: CHATGPT_HOME
80
+ });
81
+ env.page = attached.page;
82
+ const snapshot = await readSurfaceSnapshot(attached.page);
83
+ const detected = detectExperienceFromSnapshot(snapshot);
84
+ const inspectionResult = await inspectConfiguration(env, { includeOptions: true });
85
+ if (!inspectionResult.ok || inspectionResult.data === undefined) {
86
+ console.log(JSON.stringify(inspectionResult, null, 2));
87
+ return 1;
88
+ }
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
+ }
104
+ catch (error) {
105
+ console.log(JSON.stringify(toCommandResult(error), null, 2));
106
+ return 1;
107
+ }
108
+ }
109
+ export function buildSurfaceProfileDraft(options, locale, snapshot, detected, inspection, observedAt = new Date().toISOString().slice(0, 10)) {
110
+ const activeEntries = Object.entries(inspection.active)
111
+ .filter((entry) => typeof entry[1] === "string");
112
+ const axisRows = inspection.experience === "work"
113
+ ? activeEntries.map(([axis, value]) => ({
114
+ axis,
115
+ label: `${titleCase(axis)} ${value}`,
116
+ value
117
+ }))
118
+ : [];
119
+ const openerLabel = inspection.experience === "chat"
120
+ ? inspection.active.intelligence ?? inspection.active.effort
121
+ : undefined;
122
+ const menuItems = Object.values(inspection.options)
123
+ .flatMap(optionsForAxis => optionsForAxis ?? [])
124
+ .map(option => ({
125
+ label: option.label,
126
+ normalized: option.id,
127
+ role: option.hasSubmenu === true ? "menuitem" : "menuitemradio",
128
+ checked: option.selected,
129
+ ...(option.hasSubmenu === undefined ? {} : { hasPopup: option.hasSubmenu })
130
+ }));
131
+ const safeComposerLabels = dedupeBounded(detected.evidence
132
+ .filter(item => item.source === "composer")
133
+ .map(item => item.label), 16);
134
+ const safeMainControls = dedupeBounded([
135
+ ...axisRows.map(row => row.label),
136
+ ...(openerLabel === undefined ? [] : [openerLabel]),
137
+ ...menuItems.map(item => item.label),
138
+ ...(inspection.selectorProfile === "work_advanced_v1" ? ["Advanced"] : [])
139
+ ], 80);
140
+ return {
141
+ schemaVersion: "chatgpt.browser_control.surface_profile.v1",
142
+ id: options.id,
143
+ observedAt,
144
+ provenance: options.provenance,
145
+ locale,
146
+ region: options.region,
147
+ accountScope: options.accountScope,
148
+ planScope: options.planScope,
149
+ workspaceScope: options.workspaceScope,
150
+ supportState: options.supportState,
151
+ snapshot: {
152
+ url: sanitizeChatGPTUrl(snapshot.url),
153
+ composerLabels: safeComposerLabels,
154
+ mainControls: safeMainControls,
155
+ mainText: detected.evidence
156
+ .filter(item => item.source === "heading")
157
+ .map(item => item.label)
158
+ .join(" ")
159
+ .slice(0, 2000)
160
+ },
161
+ panel: {
162
+ ...(openerLabel === undefined ? {} : { openerLabel }),
163
+ axisRows,
164
+ advancedVisible: inspection.selectorProfile === "work_advanced_v1"
165
+ },
166
+ menuItems,
167
+ expected: {
168
+ experience: detected.experience,
169
+ selectorProfile: inspection.selectorProfile,
170
+ availableAxes: inspection.availableAxes,
171
+ active: inspection.active
172
+ }
173
+ };
174
+ }
175
+ export function parseArgs(argv) {
176
+ let id;
177
+ let out;
178
+ let locale;
179
+ let region = "not-recorded";
180
+ let accountScope = "not-recorded";
181
+ let planScope = "not-recorded";
182
+ let workspaceScope = "not-recorded";
183
+ let supportState = "unverified";
184
+ let provenance = DEFAULT_PROVENANCE;
185
+ let tabId;
186
+ let ifMissing = "block";
187
+ for (let index = 0; index < argv.length; index += 1) {
188
+ const arg = argv[index];
189
+ if (arg === undefined)
190
+ continue;
191
+ switch (arg) {
192
+ case "--help":
193
+ case "-h":
194
+ throw new UsageError(USAGE, 0);
195
+ case "--id":
196
+ id = requiredValue(argv, ++index, arg);
197
+ break;
198
+ case "--out":
199
+ out = requiredValue(argv, ++index, arg);
200
+ break;
201
+ case "--locale":
202
+ locale = requiredValue(argv, ++index, arg);
203
+ break;
204
+ case "--region":
205
+ region = requiredValue(argv, ++index, arg);
206
+ break;
207
+ case "--account-scope":
208
+ accountScope = requiredValue(argv, ++index, arg);
209
+ break;
210
+ case "--plan-scope":
211
+ planScope = requiredValue(argv, ++index, arg);
212
+ break;
213
+ case "--workspace-scope":
214
+ workspaceScope = requiredValue(argv, ++index, arg);
215
+ break;
216
+ case "--support-state":
217
+ supportState = parseSupportState(requiredValue(argv, ++index, arg));
218
+ break;
219
+ case "--provenance":
220
+ provenance = requiredValue(argv, ++index, arg);
221
+ break;
222
+ case "--tab-id":
223
+ tabId = requiredValue(argv, ++index, arg);
224
+ break;
225
+ case "--if-missing":
226
+ ifMissing = parseIfMissing(requiredValue(argv, ++index, arg));
227
+ break;
228
+ default:
229
+ throw new UsageError(`Unknown argument: ${arg}\n\n${USAGE}`);
230
+ }
231
+ }
232
+ if (id === undefined) {
233
+ throw new UsageError(`--id is required.\n\n${USAGE}`);
234
+ }
235
+ for (const [field, value] of Object.entries({
236
+ id,
237
+ region,
238
+ accountScope,
239
+ planScope,
240
+ workspaceScope
241
+ })) {
242
+ assertNormalizedSlug(value, `--${field.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}`);
243
+ }
244
+ const defaultOut = resolve(process.cwd(), "..", "..", "outputs", "surface-profiles", `${new Date().toISOString().slice(0, 10)}-${id}.json`);
245
+ return {
246
+ id,
247
+ out: resolve(out ?? defaultOut),
248
+ ...(locale === undefined ? {} : { locale }),
249
+ region,
250
+ accountScope,
251
+ planScope,
252
+ workspaceScope,
253
+ supportState,
254
+ provenance,
255
+ ...(tabId === undefined ? {} : { tabId }),
256
+ ifMissing
257
+ };
258
+ }
259
+ async function readDocumentLocale(page) {
260
+ if (page.evaluate === undefined)
261
+ return undefined;
262
+ return page.evaluate(() => document.documentElement.lang || undefined).catch(() => undefined);
263
+ }
264
+ function sanitizeChatGPTUrl(value) {
265
+ const url = new URL(value || CHATGPT_HOME, CHATGPT_HOME);
266
+ url.search = "";
267
+ url.hash = "";
268
+ if (/^\/c\/[^/]+/.test(url.pathname)) {
269
+ url.pathname = url.pathname.replace(/^\/c\/[^/]+/, "/c/sanitized");
270
+ }
271
+ return `${url.origin}${url.pathname}`;
272
+ }
273
+ function titleCase(value) {
274
+ return value.length === 0 ? value : `${value[0].toUpperCase()}${value.slice(1)}`;
275
+ }
276
+ function dedupeBounded(values, limit) {
277
+ return Array.from(new Set(values.map(value => value.trim()).filter(Boolean))).slice(0, limit);
278
+ }
279
+ function requiredValue(argv, index, flag) {
280
+ const value = argv[index];
281
+ if (value === undefined || value.startsWith("--")) {
282
+ throw new UsageError(`${flag} requires a value.`);
283
+ }
284
+ return value;
285
+ }
286
+ function parseSupportState(value) {
287
+ if (["current", "compatibility", "unverified", "retired"].includes(value)) {
288
+ return value;
289
+ }
290
+ throw new UsageError(`Invalid --support-state: ${value}`);
291
+ }
292
+ function parseIfMissing(value) {
293
+ if (value === "block" || value === "open" || value === "create")
294
+ return value;
295
+ throw new UsageError(`Invalid --if-missing: ${value}`);
296
+ }
297
+ function assertNormalizedSlug(value, flag) {
298
+ if (!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value)) {
299
+ throw new UsageError(`${flag} must be a normalized lowercase slug without private names.`);
300
+ }
301
+ }
302
+ if (import.meta.url === `file://${process.argv[1]}`) {
303
+ process.exitCode = await main();
304
+ }
@@ -1,7 +1,7 @@
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, newThread, openThread, readLatest, runSequence, searchThreads, selectTool, setMode, submitMessage, twoTurnExchange, waitAndRead, waitForMessage } from "../../index.js";
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
5
  import { readAssistantGenerationState } from "../../dom/generation-state.js";
6
6
  import { contextEnvFlag, contextEnvText } from "./harness.js";
7
7
  export const requiredScenarios = [
@@ -496,6 +496,39 @@ export const optionalScenarios = [
496
496
  ? pass(meta, waited, details)
497
497
  : fail(meta, waited, details);
498
498
  }),
499
+ scenario("running-status-detection", false, context => contextEnvFlag(context, "CHATGPT_E2E_RUNNING_STATUS"), async (context, meta) => {
500
+ const env = await bootNewThread(context, meta);
501
+ if ("status" in env)
502
+ return env;
503
+ let status;
504
+ try {
505
+ const asked = await askMessage(env, {
506
+ text: [
507
+ "Live running-status stress test. Write exactly 500 numbered items.",
508
+ "Each item should be a complete sentence. Continue until item 500."
509
+ ].join("\n"),
510
+ wait: false,
511
+ read: false
512
+ });
513
+ if (!asked.ok)
514
+ return fail(meta, asked);
515
+ await waitForGenerationSignal(env, 30000);
516
+ status = await messageStatus(env, { maxPreviewChars: 400 });
517
+ const data = status.data;
518
+ const details = {
519
+ completionState: data?.completionState,
520
+ generationActive: data?.generationActive,
521
+ latestAssistantTextLength: data?.latestAssistantTextLength,
522
+ status: status.status
523
+ };
524
+ return status.ok && (data?.completionState === "generating" || data?.generationActive === true)
525
+ ? pass(meta, status, details)
526
+ : fail(meta, status, details);
527
+ }
528
+ finally {
529
+ await stopGenerationIfVisible(env);
530
+ }
531
+ }),
499
532
  scenario("download-generated-file", false, context => contextEnvFlag(context, "CHATGPT_E2E_DOWNLOAD"), async (context, meta) => {
500
533
  const env = await bootNewThread(context, meta);
501
534
  if ("status" in env)
@@ -44,6 +44,7 @@ else {
44
44
  CHATGPT_E2E_DOWNLOAD: envText("CHATGPT_E2E_DOWNLOAD"),
45
45
  CHATGPT_E2E_LOGIN_PROFILE: envText("CHATGPT_E2E_LOGIN_PROFILE"),
46
46
  CHATGPT_E2E_MODE_LABEL: envText("CHATGPT_E2E_MODE_LABEL"),
47
+ CHATGPT_E2E_RUNNING_STATUS: envText("CHATGPT_E2E_RUNNING_STATUS"),
47
48
  CHATGPT_E2E_STREAM: envText("CHATGPT_E2E_STREAM"),
48
49
  CHATGPT_E2E_UPLOAD_PERMISSION_MANUAL: envText("CHATGPT_E2E_UPLOAD_PERMISSION_MANUAL"),
49
50
  CHATGPT_E2E_WEB_SEARCH: envText("CHATGPT_E2E_WEB_SEARCH")