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.
Files changed (217) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/contracts/v1/fixtures/backend-capabilities.json +1 -0
  3. package/contracts/v1/fixtures/command-descriptors.json +29 -2
  4. package/contracts/v1/fixtures/doctor-bridge-upload.json +2 -2
  5. package/contracts/v1/fixtures/doctor-scenario-preflight.json +26 -4
  6. package/contracts/v1/fixtures/files-attach-indeterminate.json +30 -0
  7. package/contracts/v1/fixtures/help-root.json +1 -1
  8. package/contracts/v1/fixtures/messages-stop-indeterminate.json +31 -0
  9. package/contracts/v1/fixtures/messages-stop-needs-confirmation.json +20 -0
  10. package/contracts/v1/fixtures/messages-stop-noop.json +19 -0
  11. package/contracts/v1/fixtures/messages-stop-success.json +21 -0
  12. package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +3 -3
  13. package/contracts/v1/fixtures/reports-create-redacted.json +3 -3
  14. package/contracts/v1/fixtures/reports-summarize-redacted.json +1 -1
  15. package/contracts/v1/fixtures/run-browser-bridge-blocker.json +12 -12
  16. package/contracts/v1/fixtures/stream-blocked.ndjson +2 -2
  17. package/contracts/v1/manifest.json +25 -0
  18. package/contracts/v1/parity-suite.json +19 -1
  19. package/dist/codex-chatgpt-control-backend.mjs +3654 -603
  20. package/dist/codex-chatgpt-control.bundle.mjs +3627 -546
  21. package/dist/src/backend/client.d.ts +2 -1
  22. package/dist/src/backend/client.js +1 -0
  23. package/dist/src/backend/protocol.d.ts +1 -1
  24. package/dist/src/backend/protocol.js +1 -0
  25. package/dist/src/backend/session.js +2 -0
  26. package/dist/src/browser/attach.d.ts +1 -0
  27. package/dist/src/browser/attach.js +69 -35
  28. package/dist/src/browser/chatgpt-url.d.ts +8 -0
  29. package/dist/src/browser/chatgpt-url.js +28 -0
  30. package/dist/src/browser/downloads.d.ts +1 -1
  31. package/dist/src/browser/downloads.js +13 -4
  32. package/dist/src/browser/page-state.js +211 -4
  33. package/dist/src/client.d.ts +2 -1
  34. package/dist/src/client.js +2 -1
  35. package/dist/src/commands/configuration.js +330 -73
  36. package/dist/src/commands/context.d.ts +5 -1
  37. package/dist/src/commands/context.js +2 -2
  38. package/dist/src/commands/conversation.js +13 -1
  39. package/dist/src/commands/doctor.js +24 -2
  40. package/dist/src/commands/experience.d.ts +1 -0
  41. package/dist/src/commands/experience.js +156 -23
  42. package/dist/src/commands/files.d.ts +1 -0
  43. package/dist/src/commands/files.js +877 -170
  44. package/dist/src/commands/messages.d.ts +2 -1
  45. package/dist/src/commands/messages.js +310 -2
  46. package/dist/src/commands/modes.js +197 -52
  47. package/dist/src/commands/registry.js +8 -0
  48. package/dist/src/commands/sequence.js +3 -1
  49. package/dist/src/commands/session.d.ts +4 -1
  50. package/dist/src/commands/session.js +34 -3
  51. package/dist/src/dom/generation-state.d.ts +6 -1
  52. package/dist/src/dom/generation-state.js +206 -45
  53. package/dist/src/dom/locale/am.d.ts +23 -1
  54. package/dist/src/dom/locale/am.js +23 -1
  55. package/dist/src/dom/locale/ar.d.ts +21 -1
  56. package/dist/src/dom/locale/ar.js +24 -4
  57. package/dist/src/dom/locale/bg.d.ts +19 -1
  58. package/dist/src/dom/locale/bg.js +22 -4
  59. package/dist/src/dom/locale/bn.d.ts +23 -1
  60. package/dist/src/dom/locale/bn.js +23 -1
  61. package/dist/src/dom/locale/bs.d.ts +17 -1
  62. package/dist/src/dom/locale/bs.js +21 -5
  63. package/dist/src/dom/locale/ca.d.ts +20 -1
  64. package/dist/src/dom/locale/ca.js +25 -6
  65. package/dist/src/dom/locale/cs.d.ts +17 -1
  66. package/dist/src/dom/locale/cs.js +17 -1
  67. package/dist/src/dom/locale/da.d.ts +17 -1
  68. package/dist/src/dom/locale/da.js +18 -2
  69. package/dist/src/dom/locale/de.d.ts +16 -1
  70. package/dist/src/dom/locale/de.js +18 -3
  71. package/dist/src/dom/locale/el.d.ts +18 -1
  72. package/dist/src/dom/locale/el.js +21 -4
  73. package/dist/src/dom/locale/en.d.ts +2 -1
  74. package/dist/src/dom/locale/en.js +2 -1
  75. package/dist/src/dom/locale/es-419.d.ts +18 -1
  76. package/dist/src/dom/locale/es-419.js +18 -1
  77. package/dist/src/dom/locale/es-ES.d.ts +18 -1
  78. package/dist/src/dom/locale/es-ES.js +18 -1
  79. package/dist/src/dom/locale/et.d.ts +16 -1
  80. package/dist/src/dom/locale/et.js +16 -1
  81. package/dist/src/dom/locale/fa.d.ts +21 -1
  82. package/dist/src/dom/locale/fa.js +23 -3
  83. package/dist/src/dom/locale/fi.d.ts +18 -1
  84. package/dist/src/dom/locale/fi.js +18 -1
  85. package/dist/src/dom/locale/fr-CA.d.ts +15 -1
  86. package/dist/src/dom/locale/fr-CA.js +15 -1
  87. package/dist/src/dom/locale/fr-FR.d.ts +18 -1
  88. package/dist/src/dom/locale/fr-FR.js +22 -5
  89. package/dist/src/dom/locale/gu.d.ts +23 -1
  90. package/dist/src/dom/locale/gu.js +27 -5
  91. package/dist/src/dom/locale/hi.d.ts +23 -1
  92. package/dist/src/dom/locale/hi.js +25 -3
  93. package/dist/src/dom/locale/hr.d.ts +16 -1
  94. package/dist/src/dom/locale/hr.js +19 -4
  95. package/dist/src/dom/locale/hu.d.ts +17 -1
  96. package/dist/src/dom/locale/hu.js +20 -4
  97. package/dist/src/dom/locale/hy.d.ts +23 -1
  98. package/dist/src/dom/locale/hy.js +26 -4
  99. package/dist/src/dom/locale/id.d.ts +17 -1
  100. package/dist/src/dom/locale/id.js +19 -3
  101. package/dist/src/dom/locale/index.js +1 -0
  102. package/dist/src/dom/locale/is.d.ts +21 -1
  103. package/dist/src/dom/locale/is.js +21 -1
  104. package/dist/src/dom/locale/it.d.ts +15 -1
  105. package/dist/src/dom/locale/it.js +20 -6
  106. package/dist/src/dom/locale/ja.d.ts +19 -1
  107. package/dist/src/dom/locale/ja.js +23 -5
  108. package/dist/src/dom/locale/ka.d.ts +23 -1
  109. package/dist/src/dom/locale/ka.js +23 -1
  110. package/dist/src/dom/locale/kk.d.ts +23 -1
  111. package/dist/src/dom/locale/kk.js +25 -3
  112. package/dist/src/dom/locale/kn.d.ts +22 -1
  113. package/dist/src/dom/locale/kn.js +26 -5
  114. package/dist/src/dom/locale/ko.d.ts +17 -1
  115. package/dist/src/dom/locale/ko.js +17 -1
  116. package/dist/src/dom/locale/lt.d.ts +18 -1
  117. package/dist/src/dom/locale/lt.js +20 -3
  118. package/dist/src/dom/locale/lv.d.ts +18 -1
  119. package/dist/src/dom/locale/lv.js +18 -1
  120. package/dist/src/dom/locale/mk.d.ts +22 -1
  121. package/dist/src/dom/locale/mk.js +26 -5
  122. package/dist/src/dom/locale/ml.d.ts +23 -1
  123. package/dist/src/dom/locale/ml.js +25 -3
  124. package/dist/src/dom/locale/mn.d.ts +23 -1
  125. package/dist/src/dom/locale/mn.js +23 -1
  126. package/dist/src/dom/locale/mr.d.ts +23 -1
  127. package/dist/src/dom/locale/mr.js +25 -3
  128. package/dist/src/dom/locale/ms.d.ts +20 -1
  129. package/dist/src/dom/locale/ms.js +20 -1
  130. package/dist/src/dom/locale/my.d.ts +22 -1
  131. package/dist/src/dom/locale/my.js +24 -3
  132. package/dist/src/dom/locale/nb.d.ts +17 -1
  133. package/dist/src/dom/locale/nb.js +17 -1
  134. package/dist/src/dom/locale/nl.d.ts +20 -2
  135. package/dist/src/dom/locale/nl.js +22 -4
  136. package/dist/src/dom/locale/pa.d.ts +23 -1
  137. package/dist/src/dom/locale/pa.js +27 -5
  138. package/dist/src/dom/locale/pl.d.ts +17 -1
  139. package/dist/src/dom/locale/pl.js +19 -3
  140. package/dist/src/dom/locale/pt-BR.d.ts +18 -1
  141. package/dist/src/dom/locale/pt-BR.js +20 -3
  142. package/dist/src/dom/locale/pt-PT.d.ts +18 -1
  143. package/dist/src/dom/locale/pt-PT.js +22 -5
  144. package/dist/src/dom/locale/ro.d.ts +15 -1
  145. package/dist/src/dom/locale/ro.js +18 -4
  146. package/dist/src/dom/locale/ru.d.ts +23 -1
  147. package/dist/src/dom/locale/ru.js +24 -2
  148. package/dist/src/dom/locale/sk.d.ts +17 -1
  149. package/dist/src/dom/locale/sk.js +19 -3
  150. package/dist/src/dom/locale/sl.d.ts +21 -1
  151. package/dist/src/dom/locale/sl.js +24 -4
  152. package/dist/src/dom/locale/so.d.ts +22 -1
  153. package/dist/src/dom/locale/so.js +24 -3
  154. package/dist/src/dom/locale/sq.d.ts +21 -1
  155. package/dist/src/dom/locale/sq.js +25 -5
  156. package/dist/src/dom/locale/sr.d.ts +24 -1
  157. package/dist/src/dom/locale/sr.js +25 -2
  158. package/dist/src/dom/locale/sv.d.ts +16 -1
  159. package/dist/src/dom/locale/sv.js +16 -1
  160. package/dist/src/dom/locale/sw.d.ts +18 -1
  161. package/dist/src/dom/locale/sw.js +18 -1
  162. package/dist/src/dom/locale/ta.d.ts +23 -1
  163. package/dist/src/dom/locale/ta.js +27 -5
  164. package/dist/src/dom/locale/te.d.ts +23 -1
  165. package/dist/src/dom/locale/te.js +27 -5
  166. package/dist/src/dom/locale/th.d.ts +11 -1
  167. package/dist/src/dom/locale/th.js +11 -1
  168. package/dist/src/dom/locale/tl.d.ts +8 -1
  169. package/dist/src/dom/locale/tl.js +8 -1
  170. package/dist/src/dom/locale/tr.d.ts +21 -1
  171. package/dist/src/dom/locale/tr.js +23 -3
  172. package/dist/src/dom/locale/types.d.ts +1 -1
  173. package/dist/src/dom/locale/uk.d.ts +23 -1
  174. package/dist/src/dom/locale/uk.js +25 -3
  175. package/dist/src/dom/locale/ur.d.ts +22 -1
  176. package/dist/src/dom/locale/ur.js +25 -4
  177. package/dist/src/dom/locale/vi.d.ts +22 -1
  178. package/dist/src/dom/locale/vi.js +25 -4
  179. package/dist/src/dom/locale/zh-HK.d.ts +21 -1
  180. package/dist/src/dom/locale/zh-HK.js +23 -3
  181. package/dist/src/dom/locale/zh-Hans.d.ts +23 -1
  182. package/dist/src/dom/locale/zh-Hans.js +27 -5
  183. package/dist/src/dom/locale/zh-TW.d.ts +23 -1
  184. package/dist/src/dom/locale/zh-TW.js +25 -3
  185. package/dist/src/dom/menus.js +26 -2
  186. package/dist/src/dom/selectors.d.ts +1 -0
  187. package/dist/src/dom/selectors.js +9 -1
  188. package/dist/src/dom/wait-snapshot.d.ts +1 -0
  189. package/dist/src/dom/wait-snapshot.js +45 -22
  190. package/dist/src/errors.d.ts +1 -1
  191. package/dist/src/errors.js +2 -2
  192. package/dist/src/platform/local-paths.d.ts +1 -0
  193. package/dist/src/platform/local-paths.js +7 -3
  194. package/dist/src/safety/risk.d.ts +1 -0
  195. package/dist/src/safety/risk.js +1 -0
  196. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +10 -1
  197. package/dist/src/scripts/apply-intelligence-locale-captures.js +219 -7
  198. package/dist/src/scripts/capture-intelligence-locales.d.ts +119 -1
  199. package/dist/src/scripts/capture-intelligence-locales.js +653 -232
  200. package/dist/src/scripts/capture-surface-profile.d.ts +3 -1
  201. package/dist/src/scripts/capture-surface-profile.js +75 -25
  202. package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
  203. package/dist/src/scripts/live-smoke/scenarios.js +321 -13
  204. package/dist/src/scripts/live-smoke.js +1 -0
  205. package/dist/src/scripts/locale-capture/surface-graph.d.ts +31 -0
  206. package/dist/src/scripts/locale-capture/surface-graph.js +50 -0
  207. package/dist/src/scripts/release-canary-module.d.ts +20 -0
  208. package/dist/src/scripts/release-canary-module.js +86 -0
  209. package/dist/src/types.d.ts +30 -6
  210. package/package.json +8 -2
  211. package/references/2026-07-16-chat-work-surfaces.md +13 -0
  212. package/references/2026-08-08-model-selection-locale-sweep.md +32 -0
  213. package/references/backend-protocol.md +22 -7
  214. package/references/language-coverage.md +24 -13
  215. package/references/localization.md +36 -14
  216. package/references/python-parity.md +9 -3
  217. package/references/troubleshooting.md +26 -4
@@ -1,5 +1,5 @@
1
1
  import { type AssistantGenerationState } from "../dom/generation-state.js";
2
- import type { AskArgs, AskReadData, CommandResult, ComposeArgs, ComposeData, MessageStatusArgs, MessageStatusData, ReadLatestArgs, ReadLatestData, RuntimeEnv, SubmitArgs, SubmitData, WaitAndReadArgs, WaitArgs, WaitData } from "../types.js";
2
+ import type { AskArgs, AskReadData, CommandResult, ComposeArgs, ComposeData, MessageStatusArgs, MessageStatusData, ReadLatestArgs, ReadLatestData, RuntimeEnv, SubmitArgs, SubmitData, StopGenerationArgs, StopGenerationData, WaitAndReadArgs, WaitArgs, WaitData } from "../types.js";
3
3
  export type CompletionSnapshot = {
4
4
  textStableForMs: number;
5
5
  stableMs: number;
@@ -10,6 +10,7 @@ export type CompletionSnapshot = {
10
10
  export declare function isResponseComplete(snapshot: CompletionSnapshot): boolean;
11
11
  export declare function composeMessage(env: RuntimeEnv, args: ComposeArgs): Promise<CommandResult<ComposeData>>;
12
12
  export declare function submitMessage(env: RuntimeEnv, args?: SubmitArgs): Promise<CommandResult<SubmitData>>;
13
+ export declare function stopGeneration(env: RuntimeEnv, args?: StopGenerationArgs): Promise<CommandResult<StopGenerationData>>;
13
14
  export declare function waitForMessage(env: RuntimeEnv, args?: WaitArgs): Promise<CommandResult<WaitData>>;
14
15
  export declare function readLatest(env: RuntimeEnv, args?: ReadLatestArgs): Promise<CommandResult<ReadLatestData>>;
15
16
  export declare function messageStatus(env: RuntimeEnv, args?: MessageStatusArgs): Promise<CommandResult<MessageStatusData>>;
@@ -3,18 +3,20 @@ import { readPageState } from "../browser/page-state.js";
3
3
  import { resultError, resultOk } from "../errors.js";
4
4
  import { EMPTY_GENERATION_STATE, latestAssistantTurnHasResponseActions, readAssistantGenerationState } from "../dom/generation-state.js";
5
5
  import { countPageMessages, isTransientAssistantText, readLatestMessage, readLatestMessageText, readLatestMessageTextSnapshot, readMessages } from "../dom/messages.js";
6
- import { composerTextbox, copyResponseButtons, sendButton } from "../dom/selectors.js";
6
+ import { composerTextbox, copyResponseButtons, sendButton, stopGenerationButton } from "../dom/selectors.js";
7
7
  import { readWaitDomSnapshot, waitTextMetadata } from "../dom/wait-snapshot.js";
8
8
  import { normalizeLineBreaks, normalizeWhitespace } from "../dom/visible-text.js";
9
9
  import { contextFromPage } from "./context.js";
10
- import { createDeadline } from "./deadline.js";
10
+ import { createDeadline, remainingMs } from "./deadline.js";
11
11
  import { withCommandOutputText } from "./output.js";
12
12
  import { createSingleFlightProbe } from "./probes.js";
13
13
  import { ensurePage } from "./session.js";
14
+ import { withTimeout } from "./timeouts.js";
14
15
  export function isResponseComplete(snapshot) {
15
16
  return snapshot.latestText.trim().length > 0
16
17
  && !isTransientAssistantText(snapshot.latestText)
17
18
  && snapshot.textStableForMs >= snapshot.stableMs
19
+ && snapshot.generation.observed
18
20
  && !snapshot.generation.active
19
21
  && !snapshot.generation.stopped
20
22
  && snapshot.hasResponseActions;
@@ -125,6 +127,311 @@ export async function submitMessage(env, args = {}) {
125
127
  return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
126
128
  }
127
129
  }
130
+ export async function stopGeneration(env, args = {}) {
131
+ const timeoutMs = Math.max(1, args.timeoutMs ?? 5_000);
132
+ const deadline = createDeadline(timeoutMs);
133
+ if (args.confirmStop !== true) {
134
+ return {
135
+ ok: false,
136
+ status: "needs_confirmation",
137
+ warnings: [],
138
+ blocker: {
139
+ kind: "confirmation",
140
+ code: "stop_generation_confirmation_required",
141
+ fieldPath: "confirmStop",
142
+ message: "Stopping the visible ChatGPT response requires an explicit caller decision.",
143
+ resumable: true
144
+ },
145
+ context: env.page === undefined
146
+ ? { timestamp: new Date().toISOString() }
147
+ : await stopContext(env.page, deadline)
148
+ };
149
+ }
150
+ let boot;
151
+ try {
152
+ boot = await withinStopDeadline(deadline, () => ensurePage(env, { minimalContext: true }), "ChatGPT page verification");
153
+ }
154
+ catch (error) {
155
+ return stopDeadlineResult(error);
156
+ }
157
+ if (!boot.ok)
158
+ return boot;
159
+ const page = env.page;
160
+ const warnings = new Set();
161
+ let stopActivationStarted = false;
162
+ let beforeActivation;
163
+ try {
164
+ const beforeProbeTimeoutMs = Math.min(1_000, Math.max(1, remainingMs(deadline)));
165
+ const beforeResult = await readStopGenerationState(page, deadline, beforeProbeTimeoutMs);
166
+ addWarnings(warnings, beforeResult.warnings);
167
+ if (!beforeResult.ok || !beforeResult.value.observed) {
168
+ if (remainingMs(deadline) <= 0) {
169
+ return stopDeadlineResult(undefined, undefined, [...warnings]);
170
+ }
171
+ return {
172
+ ok: false,
173
+ status: "blocked",
174
+ warnings: [...warnings],
175
+ blocker: {
176
+ kind: "selector_drift",
177
+ code: "stop_generation_state_unavailable",
178
+ message: "ChatGPT generation state could not be inspected safely, so no control was clicked.",
179
+ resumable: true
180
+ },
181
+ context: await stopContext(page, deadline)
182
+ };
183
+ }
184
+ const before = beforeResult.value;
185
+ beforeActivation = before;
186
+ if (!before.active) {
187
+ return resultOk({
188
+ wasGenerating: false,
189
+ stopped: false,
190
+ signalsBefore: before.signals,
191
+ signalsAfter: before.signals
192
+ }, await stopContext(page, deadline), [...warnings]);
193
+ }
194
+ const resolution = await resolveStopControl(page, deadline);
195
+ if (!resolution.ok) {
196
+ return {
197
+ ok: false,
198
+ status: "blocked",
199
+ data: { wasGenerating: true, stopped: false, signalsBefore: before.signals, signalsAfter: before.signals },
200
+ warnings: [...warnings],
201
+ blocker: {
202
+ kind: "selector_drift",
203
+ code: resolution.code,
204
+ message: resolution.message,
205
+ resumable: true
206
+ },
207
+ context: await stopContext(page, deadline)
208
+ };
209
+ }
210
+ stopActivationStarted = true;
211
+ await withinNativeStopDeadline(deadline, timeoutMs => resolution.control.click?.({ timeoutMs })
212
+ ?? Promise.reject(new Error("The resolved Stop control does not support click().")), "Stop control click");
213
+ let after = EMPTY_GENERATION_STATE;
214
+ while (remainingMs(deadline) > 0) {
215
+ const afterProbeTimeoutMs = Math.min(1_000, Math.max(1, remainingMs(deadline)));
216
+ const afterResult = await readStopGenerationState(page, deadline, afterProbeTimeoutMs);
217
+ addWarnings(warnings, afterResult.warnings);
218
+ if (afterResult.ok) {
219
+ after = afterResult.value;
220
+ if (after.observed && !after.active) {
221
+ return resultOk({
222
+ wasGenerating: true,
223
+ stopped: true,
224
+ signalsBefore: before.signals,
225
+ signalsAfter: after.signals
226
+ }, await stopContext(page, deadline), [...warnings]);
227
+ }
228
+ }
229
+ await sleepWithinDeadline(page, deadline, 100);
230
+ }
231
+ const data = {
232
+ wasGenerating: true,
233
+ stopped: false,
234
+ signalsBefore: before.signals,
235
+ signalsAfter: after.signals
236
+ };
237
+ return stopDeadlineResult(undefined, data, [...warnings]);
238
+ }
239
+ catch (error) {
240
+ if (remainingMs(deadline) <= 0 || error instanceof StopDeadlineError) {
241
+ const data = stopActivationStarted && beforeActivation !== undefined
242
+ ? {
243
+ wasGenerating: true,
244
+ stopped: false,
245
+ signalsBefore: beforeActivation.signals,
246
+ signalsAfter: beforeActivation.signals
247
+ }
248
+ : undefined;
249
+ return stopDeadlineResult(error, data, [...warnings]);
250
+ }
251
+ return resultError(error instanceof Error ? error : new Error(String(error)), await stopContext(page, deadline));
252
+ }
253
+ }
254
+ async function readStopGenerationState(page, deadline, capMs) {
255
+ const timeoutMs = Math.min(capMs, remainingMs(deadline));
256
+ if (timeoutMs <= 0) {
257
+ return {
258
+ ok: false,
259
+ timedOut: true,
260
+ warnings: ["Skipped generation state DOM probe because no deadline budget remained."]
261
+ };
262
+ }
263
+ try {
264
+ // readAssistantGenerationState propagates this one budget through its
265
+ // evaluate/content fallbacks. Await it directly so no browser request is
266
+ // abandoned behind an SDK-side Promise.race.
267
+ const value = await readAssistantGenerationState(page, { timeoutMs });
268
+ return { ok: true, value, warnings: [] };
269
+ }
270
+ catch (error) {
271
+ return {
272
+ ok: false,
273
+ warnings: [`generation state DOM probe failed: ${error instanceof Error ? error.message : String(error)}`]
274
+ };
275
+ }
276
+ }
277
+ async function resolveStopControl(page, deadline) {
278
+ const candidates = stopGenerationButton(page);
279
+ if (typeof candidates.count !== "function") {
280
+ return {
281
+ ok: false,
282
+ code: "stop_generation_control_unavailable",
283
+ message: "ChatGPT is generating, but the Stop-control candidate set could not be enumerated safely."
284
+ };
285
+ }
286
+ const count = await stopControlCount(candidates, deadline);
287
+ if (count === 0) {
288
+ return {
289
+ ok: false,
290
+ code: "stop_generation_control_unavailable",
291
+ message: "ChatGPT is generating, but no visible Stop control was found."
292
+ };
293
+ }
294
+ if (count > 8) {
295
+ return {
296
+ ok: false,
297
+ code: "stop_generation_control_ambiguous",
298
+ message: `ChatGPT exposed ${count} Stop-control candidates; refusing to choose among an unexpectedly broad set.`
299
+ };
300
+ }
301
+ const eligible = [];
302
+ for (let index = 0; index < count; index += 1) {
303
+ const control = count === 1
304
+ ? candidates
305
+ : typeof candidates.nth === "function"
306
+ ? candidates.nth(index)
307
+ : undefined;
308
+ if (control === undefined || typeof control.evaluate !== "function") {
309
+ continue;
310
+ }
311
+ const evaluationTimeoutMs = Math.min(1_000, Math.max(1, remainingMs(deadline)));
312
+ const scoped = await withinNativeStopDeadline(deadline, timeoutMs => control.evaluate((element) => {
313
+ const button = element;
314
+ if (button.closest("[hidden], [inert], [aria-hidden='true']") !== null)
315
+ return false;
316
+ if (button.disabled || button.getAttribute("aria-disabled") === "true")
317
+ return false;
318
+ const visible = (node) => {
319
+ if (node.hidden || node.closest("[hidden], [inert], [aria-hidden='true']") !== null)
320
+ return false;
321
+ const style = window.getComputedStyle(node);
322
+ const rect = node.getBoundingClientRect();
323
+ return style.display !== "none"
324
+ && style.visibility !== "hidden"
325
+ && style.opacity !== "0"
326
+ && style.pointerEvents !== "none"
327
+ && (rect.width > 0 || rect.height > 0);
328
+ };
329
+ const textboxes = Array.from(document.querySelectorAll("textarea, [contenteditable='true'], [role='textbox']")).filter(visible);
330
+ const activeComposers = [...new Set(textboxes
331
+ .map(textbox => textbox.closest("form")
332
+ ?? textbox.closest("[data-testid*='composer' i]")
333
+ ?? textbox.closest("[aria-label*='composer' i]")
334
+ ?? textbox.closest("[class*='composer' i]"))
335
+ .filter((value) => value !== null))];
336
+ return activeComposers.length === 1 && activeComposers[0].contains(button);
337
+ }, undefined, { timeoutMs }), `Stop control ${index + 1} visibility and scope check`, evaluationTimeoutMs);
338
+ if (scoped)
339
+ eligible.push(control);
340
+ }
341
+ if (eligible.length === 1) {
342
+ return { ok: true, control: eligible[0] };
343
+ }
344
+ if (eligible.length > 1) {
345
+ return {
346
+ ok: false,
347
+ code: "stop_generation_control_ambiguous",
348
+ message: `ChatGPT exposed ${eligible.length} visible Stop controls in active composer regions; no control was clicked.`
349
+ };
350
+ }
351
+ return {
352
+ ok: false,
353
+ code: "stop_generation_control_unavailable",
354
+ message: "ChatGPT is generating, but no uniquely scoped visible Stop control could be activated."
355
+ };
356
+ }
357
+ async function stopControlCount(candidates, deadline) {
358
+ if (typeof candidates.allTextContents === "function") {
359
+ const values = await withinNativeStopDeadline(deadline, timeoutMs => candidates.allTextContents({ timeoutMs }), "Stop control enumeration", 1_000);
360
+ return values.length;
361
+ }
362
+ return withinStopDeadline(deadline, () => candidates.count(), "Stop control enumeration");
363
+ }
364
+ async function withinStopDeadline(deadline, operation, label) {
365
+ const budget = remainingMs(deadline);
366
+ if (budget <= 0)
367
+ throw new StopDeadlineError(label);
368
+ try {
369
+ return await withTimeout(operation(), budget, `${label} exceeded the messages.stop deadline.`);
370
+ }
371
+ catch (error) {
372
+ if (remainingMs(deadline) <= 0)
373
+ throw new StopDeadlineError(label);
374
+ throw error;
375
+ }
376
+ }
377
+ async function withinNativeStopDeadline(deadline, operation, label, capMs = 2_000) {
378
+ const timeoutMs = Math.min(capMs, remainingMs(deadline));
379
+ if (timeoutMs <= 0)
380
+ throw new StopDeadlineError(label);
381
+ try {
382
+ // The Chrome bridge owns this timeout. Unlike Promise.race, a native
383
+ // timeout terminates the browser request instead of abandoning it in flight.
384
+ return await operation(timeoutMs);
385
+ }
386
+ catch (error) {
387
+ if (remainingMs(deadline) <= 0 || isNativeBrowserTimeout(error)) {
388
+ throw new StopDeadlineError(label);
389
+ }
390
+ throw error;
391
+ }
392
+ }
393
+ function isNativeBrowserTimeout(error) {
394
+ return error instanceof Error && /timed?\s*out|timeout/i.test(error.message);
395
+ }
396
+ async function sleepWithinDeadline(_page, deadline, requestedMs) {
397
+ const waitMs = Math.min(requestedMs, Math.max(0, remainingMs(deadline) - 1));
398
+ if (waitMs <= 0)
399
+ return;
400
+ // Polling does not require a browser round trip. A host timer cannot leave a
401
+ // bridge request stranded after the Stop deadline.
402
+ await new Promise(resolve => setTimeout(resolve, waitMs));
403
+ }
404
+ async function stopContext(page, _deadline) {
405
+ return contextFromPage(page, {}, { minimal: true });
406
+ }
407
+ function stopDeadlineResult(error, data, warnings = []) {
408
+ const message = data?.wasGenerating === true
409
+ ? "Stop activation reached its browser-native deadline and was terminated, but the click may already have taken effect. Inspect the visible generation state and do not retry automatically."
410
+ : "ChatGPT generation could not be inspected and stopped before the single operation deadline.";
411
+ const result = {
412
+ ok: false,
413
+ status: "timeout",
414
+ warnings: error === undefined
415
+ ? warnings
416
+ : [...warnings, error instanceof Error ? error.message : String(error)],
417
+ blocker: {
418
+ kind: "selector_drift",
419
+ code: data?.wasGenerating === true ? "stop_generation_unverified" : "stop_generation_deadline_exhausted",
420
+ message,
421
+ resumable: data?.wasGenerating !== true
422
+ },
423
+ context: { timestamp: new Date().toISOString() }
424
+ };
425
+ if (data !== undefined)
426
+ result.data = data;
427
+ return result;
428
+ }
429
+ class StopDeadlineError extends Error {
430
+ constructor(label) {
431
+ super(`${label} could not complete before the messages.stop deadline.`);
432
+ this.name = "StopDeadlineError";
433
+ }
434
+ }
128
435
  async function clickSendControl(page) {
129
436
  try {
130
437
  await sendButton(page).click?.();
@@ -355,6 +662,7 @@ export async function waitForMessage(env, args = {}) {
355
662
  && snapshot.text.length > 0
356
663
  && !snapshot.text.transient
357
664
  && Date.now() - lastChangedAt >= stableMs
665
+ && snapshot.generation.observed
358
666
  && !snapshot.generation.active
359
667
  && !snapshot.generation.stopped
360
668
  && hasResponseActions;