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
@@ -0,0 +1,20 @@
1
+ import type { BrowserLike } from "../types.js";
2
+ import type { LiveSmokeScenarioResult } from "./live-smoke/types.js";
3
+ export type ReleaseCanaryOptions = {
4
+ tabId: string;
5
+ reportDir?: string;
6
+ includeUpload?: boolean;
7
+ };
8
+ export type ReleaseCanaryResult = {
9
+ ok: boolean;
10
+ profilePaths: string[];
11
+ reportPath?: string;
12
+ results: LiveSmokeScenarioResult[];
13
+ failures: string[];
14
+ };
15
+ type ReleaseCanaryRuntime = {
16
+ agent?: unknown;
17
+ browser?: BrowserLike;
18
+ };
19
+ export declare function runReleaseCanary(runtime: ReleaseCanaryRuntime, options: ReleaseCanaryOptions): Promise<ReleaseCanaryResult>;
20
+ export {};
@@ -0,0 +1,86 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { main as captureSurfaceProfile } from "./capture-surface-profile.js";
4
+ import { filterScenarios, runLiveSmoke } from "./live-smoke/harness.js";
5
+ import { optionalScenarios, requiredScenarios } from "./live-smoke/scenarios.js";
6
+ const CORE_SCENARIOS = [
7
+ "chat-work-expansion",
8
+ "configuration-mutate-restore",
9
+ "download-generated-file",
10
+ ];
11
+ export async function runReleaseCanary(runtime, options) {
12
+ if (runtime.agent === undefined || runtime.agent === null) {
13
+ throw new Error("runReleaseCanary must run in a Codex bridge-hosted JavaScript context.");
14
+ }
15
+ if (options.tabId.trim().length === 0) {
16
+ throw new Error("runReleaseCanary requires an exact dedicated ChatGPT tab id.");
17
+ }
18
+ const reportDir = resolve(options.reportDir ?? join(process.cwd(), "reports", "release-canary"));
19
+ const profileDir = join(reportDir, "surface-profiles");
20
+ await mkdir(profileDir, { recursive: true });
21
+ const stamp = new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
22
+ const profilePaths = [
23
+ join(profileDir, `${stamp}-chat.json`),
24
+ join(profileDir, `${stamp}-work.json`),
25
+ ];
26
+ try {
27
+ for (const [index, experience] of ["chat", "work"].entries()) {
28
+ const exitCode = await captureSurfaceProfile([
29
+ "--id", `release-canary-${experience}`,
30
+ "--experience", experience,
31
+ "--tab-id", options.tabId,
32
+ "--if-missing", "block",
33
+ "--out", profilePaths[index],
34
+ "--provenance", "Sanitized release canary capture from a dedicated visible ChatGPT tab."
35
+ ], runtime);
36
+ if (exitCode !== 0) {
37
+ return {
38
+ ok: false,
39
+ profilePaths: profilePaths.slice(0, index),
40
+ results: [],
41
+ failures: [`surface-profile-${experience}`]
42
+ };
43
+ }
44
+ }
45
+ }
46
+ finally {
47
+ await closeDedicatedProfileTab(runtime.browser, options.tabId);
48
+ }
49
+ const names = options.includeUpload === true
50
+ ? [...CORE_SCENARIOS, "attach-one-file"]
51
+ : CORE_SCENARIOS;
52
+ const context = {
53
+ agent: runtime.agent,
54
+ ...(runtime.browser === undefined ? {} : { browser: runtime.browser }),
55
+ reportDir: join(reportDir, "live-smoke"),
56
+ env: {
57
+ CHATGPT_E2E_CONFIGURATION_MUTATION: "1",
58
+ CHATGPT_E2E_DOWNLOAD: "1",
59
+ }
60
+ };
61
+ const scenarios = filterScenarios([...requiredScenarios, ...optionalScenarios], names.join(","));
62
+ if (scenarios.length !== names.length) {
63
+ throw new Error(`Release canary scenario registration drift: expected ${names.length}, found ${scenarios.length}.`);
64
+ }
65
+ const smoke = await runLiveSmoke(context, scenarios);
66
+ const failures = smoke.results.filter(result => result.status !== "pass").map(result => result.name);
67
+ return {
68
+ ok: failures.length === 0,
69
+ profilePaths,
70
+ reportPath: smoke.reportPath,
71
+ results: smoke.results,
72
+ failures
73
+ };
74
+ }
75
+ async function closeDedicatedProfileTab(browser, tabId) {
76
+ const tabs = browser?.tabs;
77
+ const get = tabs?.get;
78
+ if (tabs === undefined || typeof get !== "function") {
79
+ throw new Error("Release canary requires browser.tabs.get so its dedicated profile tab can be closed before behavior tests.");
80
+ }
81
+ const tab = await get.call(tabs, tabId);
82
+ if (typeof tab.close !== "function") {
83
+ throw new Error("Release canary dedicated profile tab does not expose close().");
84
+ }
85
+ await tab.close();
86
+ }
@@ -432,6 +432,17 @@ export type DownloadLatestArgs = {
432
432
  };
433
433
  timeoutMs?: number;
434
434
  };
435
+ export type StopGenerationArgs = {
436
+ /** Required acknowledgement because stopping changes the visible Chat turn. */
437
+ confirmStop?: boolean;
438
+ timeoutMs?: number;
439
+ };
440
+ export type StopGenerationData = {
441
+ wasGenerating: boolean;
442
+ stopped: boolean;
443
+ signalsBefore: string[];
444
+ signalsAfter: string[];
445
+ };
435
446
  export type DownloadedFile = {
436
447
  path: string;
437
448
  suggestedFilename?: string;
@@ -767,6 +778,10 @@ export type SequenceStep = {
767
778
  id: string;
768
779
  command: "messages.status";
769
780
  args?: MessageStatusArgs;
781
+ } | {
782
+ id: string;
783
+ command: "messages.stop";
784
+ args: StopGenerationArgs;
770
785
  } | {
771
786
  id: string;
772
787
  command: "messages.waitAndRead";
@@ -869,27 +884,33 @@ export type BrowserUserTabInfo = {
869
884
  title?: string;
870
885
  url?: string;
871
886
  };
887
+ export type BrowserOperationOptions = {
888
+ timeoutMs?: number;
889
+ };
872
890
  export type LocatorLike = {
873
- click?: (options?: unknown) => Promise<void>;
891
+ click?: (options?: BrowserOperationOptions & Record<string, unknown>) => Promise<void>;
892
+ press?: (key: string, options?: unknown) => Promise<void>;
874
893
  fill?: (value: string, options?: unknown) => Promise<void>;
875
894
  textContent?: (options?: unknown) => Promise<string | null>;
876
895
  innerText?: (options?: unknown) => Promise<string>;
877
896
  innerHTML?: (options?: unknown) => Promise<string>;
878
897
  count?: () => Promise<number>;
898
+ allTextContents?: (options?: BrowserOperationOptions) => Promise<string[]>;
879
899
  nth?: (index: number) => LocatorLike;
880
900
  first?: () => LocatorLike;
881
901
  last?: () => LocatorLike;
882
902
  isVisible?: (options?: unknown) => Promise<boolean>;
883
- evaluate?: <T>(fn: (element: Element) => T) => Promise<T>;
903
+ evaluate?: <T>(fn: (element: Element) => T, arg?: unknown, options?: BrowserOperationOptions) => Promise<T>;
884
904
  locator?: (selector: string) => LocatorLike;
885
905
  filter?: (options: Record<string, unknown>) => LocatorLike;
886
906
  getByRole?: (role: string, options?: Record<string, unknown>) => LocatorLike;
887
907
  getByText?: (text: string | RegExp, options?: Record<string, unknown>) => LocatorLike;
888
- setInputFiles?: (paths: string[]) => Promise<void>;
908
+ setInputFiles?: (paths: string[], options?: BrowserOperationOptions) => Promise<void>;
889
909
  };
890
910
  export type FileChooserLike = {
911
+ element?: () => LocatorLike | Promise<LocatorLike>;
891
912
  isMultiple?: () => boolean | Promise<boolean>;
892
- setFiles: (paths: string[]) => Promise<void>;
913
+ setFiles: (paths: string[], options?: BrowserOperationOptions) => Promise<void>;
893
914
  };
894
915
  export type WaitForEventOptions = {
895
916
  timeout?: number;
@@ -922,11 +943,14 @@ export type PageLike = {
922
943
  y: number;
923
944
  button?: number;
924
945
  }) => Promise<void> | void;
946
+ keypress?: (options: {
947
+ keys: string[];
948
+ }) => Promise<void> | void;
925
949
  };
926
950
  waitForTimeout?: (ms: number) => Promise<void>;
927
951
  waitForEvent?: (event: string, optionsOrCallback?: WaitForEventOptions | unknown) => Promise<unknown>;
928
- evaluate?: <T, A = unknown>(fn: (arg: A) => T | Promise<T>, arg?: A) => Promise<T>;
929
- content?: () => Promise<string>;
952
+ evaluate?: <T, A = unknown>(fn: (arg: A) => T | Promise<T>, arg?: A, options?: BrowserOperationOptions) => Promise<T>;
953
+ content?: (options?: BrowserOperationOptions) => Promise<string>;
930
954
  close?: () => Promise<void>;
931
955
  capabilities?: {
932
956
  get?: (id: string) => Promise<unknown> | unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-chatgpt-control",
3
- "version": "0.5.0-alpha.1",
3
+ "version": "0.5.1-alpha.2",
4
4
  "description": "Unofficial SDK for controlling visible ChatGPT Chat and Work sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,6 +26,8 @@
26
26
  "bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/codex-chatgpt-control.bundle.mjs",
27
27
  "bundle:backend": "esbuild src/scripts/backend-server.ts --bundle --platform=node --format=esm --outfile=dist/codex-chatgpt-control-backend.mjs",
28
28
  "bundle:live-smoke": "esbuild src/scripts/live-smoke-module.ts --bundle --platform=node --format=esm --outfile=dist/codex-chatgpt-control-live-smoke.bundle.mjs",
29
+ "bundle:release-canary": "esbuild src/scripts/release-canary-module.ts --bundle --platform=node --format=esm --outfile=dist/codex-chatgpt-control-release-canary.bundle.mjs",
30
+ "contract:generate": "node scripts/generate-contract-fixtures.mjs",
29
31
  "contract:validate": "node scripts/validate-contract.mjs",
30
32
  "docs:drift": "tsx src/scripts/check-surface-drift.ts",
31
33
  "parity:fixtures": "node scripts/check-parity-fixtures.mjs",
@@ -34,11 +36,15 @@
34
36
  "test": "vitest run",
35
37
  "test:watch": "vitest",
36
38
  "thread": "tsx src/scripts/continue-thread.ts",
39
+ "capture:intelligence-locales": "tsx src/scripts/capture-intelligence-locales.ts",
40
+ "capture:intelligence-locales:queue": "tsx src/scripts/capture-intelligence-locales.ts --print-queue",
37
41
  "capture:surface-profile": "tsx src/scripts/capture-surface-profile.ts",
42
+ "apply:intelligence-locales": "tsx src/scripts/apply-intelligence-locale-captures.ts",
38
43
  "smoke:hi": "tsx src/scripts/smoke-hi.ts",
39
44
  "smoke:search": "tsx src/scripts/smoke-search-open-read.ts",
40
45
  "smoke:attach": "tsx src/scripts/smoke-attach-file.ts",
41
- "smoke:live": "tsx src/scripts/live-smoke.ts"
46
+ "smoke:live": "tsx src/scripts/live-smoke.ts",
47
+ "smoke:release:build": "npm run bundle:release-canary"
42
48
  },
43
49
  "exports": {
44
50
  ".": {
@@ -50,6 +50,19 @@ Legacy `modes.set/get` and runner `mode` inputs remain supported. They preserve
50
50
  their pre-0.5 warning-oriented behavior. New code requiring a verified
51
51
  postcondition should use `configuration.apply({ strict: true })`.
52
52
 
53
+ ## Surface Transitions
54
+
55
+ On the current home surface, ChatGPT exposes Chat and Work as a radio group in
56
+ the page banner. The checked radio is the strongest experience signal; the
57
+ textbox accessible name is shared and cannot classify the surface by itself.
58
+ Older button, tab, link, and menu-item controls remain compatibility fallbacks.
59
+
60
+ An active Work task may remove the home radio group while retaining the Work
61
+ model-plus-effort opener, such as `5.5 Light`. That compound control is a Work
62
+ continuation signal. To switch panes from an active conversation, the SDK
63
+ returns to the stable ChatGPT home surface and selects the requested radio; it
64
+ does not infer or navigate to a private Work route.
65
+
53
66
  ## Work Lifecycle
54
67
 
55
68
  `work.start` defaults to `newTask: true`. If messages are already loaded and a
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: Model Selection Locale Sweep
3
+ date: 2026-08-08
4
+ type: review
5
+ status: complete
6
+ ---
7
+
8
+ # Model Selection Locale Sweep
9
+
10
+ English plus all 64 non-English languages advertised in ChatGPT Settings were
11
+ captured from one authenticated live account against the current selector UI.
12
+
13
+ The latest record for every locale passed these gates:
14
+
15
+ - rendered `html lang` matched the requested language;
16
+ - Chat exposed Power plus ordered Model/Effort rows;
17
+ - Work exposed Power plus ordered Model/Effort/Speed rows;
18
+ - Chat contained 3 model and 5 effort options;
19
+ - Work contained 4 model, 6 effort, and 2 speed options;
20
+ - captured labels were non-empty;
21
+ - Chat and the initial rendered language were restored.
22
+
23
+ The append-only private-source evidence contains 77 attempts: 65 latest
24
+ successes and 12 earlier blocked attempts retained for diagnostics. Every
25
+ blocked locale was subsequently recaptured successfully. The reviewed apply
26
+ updated all 64 non-English locale contributions and was idempotent on a second
27
+ run.
28
+
29
+ This is point-in-time evidence for the account and rollout observed on
30
+ 2026-08-08. It does not guarantee identical model inventory for every plan,
31
+ region, workspace, or experiment. Generation-state, project, and plugin labels
32
+ were outside this no-prompt sweep.
@@ -71,6 +71,10 @@ Browser-control blockers are not protocol errors. They are normal command or run
71
71
 
72
72
  Use `messages.status({ maxPreviewChars })` for a compact latest-assistant progress snapshot when a host tool-call ceiling is shorter than the expected ChatGPT generation. It returns counts, latest-assistant preview length, `completionState`, `generationActive`, and generation signals without treating partial text as final, and without the cost of a full `readLatest`/`wait` probe.
73
73
 
74
+ Use `messages.stop({ confirmStop: true })` only after the caller explicitly decides that the current visible response should stop. It clicks one uniquely scoped, visible stop control and succeeds only after generation is observed inactive. Without the exact boolean `confirmStop: true`, it returns `needs_confirmation`; if generation is observably inactive, it is a no-op. An unavailable or ambiguous control, an uninspectable generation state, or an unverified postcondition fails closed.
75
+
76
+ If the Stop activation starts but its completion races the command deadline, the result is `status: "timeout"` with blocker code `stop_generation_unverified` and `resumable: false`. The browser-native deadline terminates the request before the command returns, so it cannot click later; the click may already have taken effect before termination. Inspect the current visible generation state and never retry Stop automatically.
77
+
74
78
  ## Streaming
75
79
 
76
80
  Streaming commands emit backend event lines until `completed` or `error`.
@@ -134,6 +138,10 @@ Use `files.preflight` for non-mutating local validation before browser upload wo
134
138
 
135
139
  `files.attach` accepts `includeDiagnostics: true` to return metadata-only upload diagnostics in `data.diagnostics`: the preflight result plus the browser input's selected file names and sizes when the DOM exposes them. Pair `includeDiagnostics: true` with `includeHashes: true` when diagnosing whether a non-empty local file became an empty browser-side `File`; do not persist these diagnostics in public reports unless the user has approved content fingerprint metadata.
136
140
 
141
+ Once the native file handoff starts, any timeout, bridge error, or missing post-handoff composer evidence is indeterminate: `files.attach` returns `status: "partial"`, blocker code `attachment_outcome_indeterminate`, and `resumable: false`. Browser-native deadlines terminate the handoff request before the command returns, so it cannot mutate later; the file may already be present. Inspect the current composer, do not submit, and never retry the attachment automatically.
142
+
143
+ The Codex Chrome chooser intentionally exposes `isMultiple()` and `setFiles()` but no backing-element accessor. Before accepting that opaque chooser, the Node runtime proves the initiating input or control belongs to the unique active composer; the CDP fallback independently resolves and clicks that exact unique input. Browser providers that expose a chooser backing element receive an additional identity cross-check before handoff.
144
+
137
145
  ## Project Sources
138
146
 
139
147
  Project Sources V1 is a narrow visible-UI surface for ChatGPT Project URLs such as `https://chatgpt.com/g/g-p-.../project`.
@@ -164,9 +172,16 @@ capability to inventory and bundle the latest non-SVG image asset. This is an
164
172
  implementation detail of the TypeScript runtime; the wire command and result
165
173
  shape are unchanged.
166
174
 
167
- `files.downloadLatest` preserves the existing file-link behavior and falls back
168
- to generated-artifact download only when no conventional ChatGPT file affordance
169
- is visible. Artifact failures are reported as structured blockers such as
175
+ `files.downloadLatest` preserves the existing file-link behavior, recognizes
176
+ filename-labelled buttons in the latest assistant turn, opens ChatGPT's artifact
177
+ preview, and then activates its visible Download control. `filenamePattern` is
178
+ an optional case-insensitive regular expression that prevents a different file
179
+ or image fallback from being accepted as the requested output. When the browser
180
+ bridge exposes only a completed local download `path()`, the runtime copies that
181
+ file into `destDir`; bridges exposing `saveAs()` remain supported. Without a
182
+ filename pattern, the command falls back to generated-artifact download only
183
+ when no conventional ChatGPT file affordance is visible. Artifact failures are
184
+ reported as structured blockers such as
170
185
  `artifact_unavailable`, `artifact_selector_drift`, or
171
186
  `artifact_download_unavailable`, not protocol errors.
172
187
 
@@ -220,13 +235,13 @@ For live browser control, the backend process must have access to a compatible b
220
235
  - Explicit `RuntimeEnv.browser` or `RuntimeEnv.page` in a future embedding.
221
236
  - A future Python-native/native-host/CDP backend that implements this same protocol.
222
237
 
223
- Important: in Codex, `globalThis.agent` is not present until the Chrome plugin runtime is bootstrapped. Do not diagnose bridge availability by checking `globalThis.agent` in an ordinary shell or before calling the Chrome plugin's `setupBrowserRuntime({ globals: globalThis })`.
238
+ Important: in Codex, `globalThis.agent` is not present until the Chrome plugin runtime is bootstrapped. Do not diagnose bridge availability by checking `globalThis.agent` in an ordinary shell or before assigning the object returned by the Chrome plugin's `setupBrowserRuntime()`.
224
239
 
225
240
  The live Chrome bootstrap is:
226
241
 
227
242
  ```js
228
- const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/latest/scripts/browser-client.mjs");
229
- await setupBrowserRuntime({ globals: globalThis });
243
+ const { setupBrowserRuntime } = await import("/absolute/path/to/the/current/chrome/scripts/browser-client.mjs");
244
+ globalThis.agent = await setupBrowserRuntime();
230
245
  globalThis.browser = await agent.browsers.get("extension");
231
246
  ```
232
247
 
@@ -270,7 +285,7 @@ python scripts/live_smoke.py \
270
285
  --backend-command "node scripts/http_stdio_relay.mjs"
271
286
  ```
272
287
 
273
- The bridge-hosted JS execution must remain active for the duration of the Python smoke. The relay path is:
288
+ Create the backend server and wait on it in the **same** bridge-hosted JS execution. A server created in one Node REPL call cannot be kept bridge-capable by waiting in a second call. That single execution must remain active for the duration of the Python smoke. The relay path is:
274
289
 
275
290
  ```text
276
291
  Python SDK -> stdio relay -> bridge-hosted Node backend -> Codex Chrome bridge -> ChatGPT
@@ -5,12 +5,10 @@ The full set of languages ChatGPT exposes in **Settings → General → Language
5
5
  localization rollout described in [`localization.md`](./localization.md). Strings are
6
6
  stored per-locale under `src/dom/locale/<bcp47>.ts`.
7
7
 
8
- The status table covers the pre-existing Chat/localization registry, not the new
9
- Chat/Work profile graph introduced in July 2026. Work composer,
10
- experience-switch, configuration-axis, and configuration-option coverage is
11
- currently verified only for the sanitized English profiles. Do not interpret a
12
- green row below as full Work support until that locale has matching profile
13
- evidence.
8
+ The status table began as the pre-existing Chat/localization registry. On
9
+ 2026-08-08, English plus all 64 non-English entries were recaptured against the
10
+ current Chat and Work Power/Advanced selector graph. The latest record for
11
+ every locale passed; see the [reviewed sweep summary](./2026-08-08-model-selection-locale-sweep.md).
14
12
 
15
13
  - Speaker counts are **crude guesstimates** (native + second-language, rounded), not
16
14
  researched figures. They exist only to prioritize the rollout.
@@ -124,21 +122,33 @@ For a single current Chat or Work surface, generate a sanitized, unverified
124
122
  profile draft from an already-open authorized tab:
125
123
 
126
124
  ```bash
127
- npm run capture:surface-profile -- --id work-basic-en --locale en-US
125
+ npm run capture:surface-profile -- --id work-basic-en --locale en-US --experience work
128
126
  ```
129
127
 
130
- This command does not submit a prompt or change configuration. It opens visible
131
- menus for inspection, normalizes any conversation URL to `/c/sanitized`, and
128
+ This command does not submit a prompt or change model configuration. When
129
+ `--experience` is supplied it visibly selects Chat or Work and restores the
130
+ prior pane by default. It opens visible menus for inspection, normalizes any conversation URL to `/c/sanitized`, and
132
131
  writes only bounded composer/configuration structure under
133
132
  `outputs/surface-profiles/`.
134
133
 
135
134
  Use the repository capture script to drive the visible ChatGPT UI through Settings → General
136
- → Language, then open the composer Intelligence picker and record the localized labels:
135
+ → Language, then open the current composer selector and record localized labels:
137
136
 
138
137
  ```bash
139
138
  npm run capture:intelligence-locales -- --auto-switch --all --if-missing open
140
139
  ```
141
140
 
141
+ For a complete current selector sweep, add `--capture-surfaces`. This reads the
142
+ two ordered pane radios, both Power controls, both Advanced labels, Chat's
143
+ ordered Model/Effort rows, Work's ordered Model/Effort/Speed rows, and every
144
+ visible submenu option. It restores the original compact/advanced state, Chat
145
+ pane, and initial rendered language:
146
+
147
+ ```bash
148
+ npm run capture:intelligence-locales -- \
149
+ --auto-switch --all --capture-surfaces --if-missing open
150
+ ```
151
+
142
152
  To also capture localized running-generation controls such as `stopControl`, run the same
143
153
  sweep with the bounded generation probe enabled:
144
154
 
@@ -166,9 +176,10 @@ changes the settings or picker DOM, the run should emit a blocker record instead
166
176
 
167
177
  1. Switch ChatGPT **Settings → General → Language** to the target, reload, and confirm via
168
178
  `document.documentElement.lang` + visible text.
169
- 2. Read static controls (composer, add-files, new chat, current model label) via JS.
170
- 3. Open the model switcher and the `+` menu with **real** clicks (Radix menus ignore
171
- synthetic `.click()`) and read the menu items → mode + tool labels.
179
+ 2. Read static controls (composer, add-files, new chat, current selector label) via JS.
180
+ 3. Open the selector with a **real** click, record the Power slider, expand
181
+ Advanced when needed, and read the ordered Chat Model/Effort or Work
182
+ Model/Effort/Speed menus. Restore the initial compact/advanced state.
172
183
  4. Open search → placeholder; observe a conversation → copy-response + response-actions.
173
184
  `stopControl` is present only mid-generation and should be captured with
174
185
  `--capture-generation-state`; login/captcha/rate-limit copy needs a logged-out/limited
@@ -71,7 +71,7 @@ Localized — lives in `src/dom/locale/en.ts` (English) and per-locale files, sa
71
71
  | `workComposerTextbox` | Work composer textbox | `aria-label` / placeholder |
72
72
  | `newWork` | start-another-Work-task control | visible button or link text |
73
73
  | `experienceOptions.chat` / `.work` | Chat/Work switch controls | visible button, tab, link, or menu text |
74
- | `configurationAxes.*` | model/intelligence/effort/speed/advanced rows | visible row text |
74
+ | `configurationAxes.*` | power/model/intelligence/effort/speed/advanced controls | visible or accessible text |
75
75
  | `configurationOptions.*` | Chat and Work configuration values | visible menu text |
76
76
  | `sendButton` | send button | `aria-label` |
77
77
  | `searchChatsButton` | search-chats button | `aria-label` |
@@ -106,18 +106,19 @@ Localized — lives in `src/dom/locale/en.ts` (English) and per-locale files, sa
106
106
  The type definitions for `LocaleStrings` (complete) and `LocaleContribution` (partial, for
107
107
  non-English files) live in [`src/dom/locale/types.ts`](../src/dom/locale/types.ts).
108
108
 
109
- The first Chat/Work profile fixtures are English evidence. The nested locale
110
- combiner lets existing locale files omit the new groups without breaking
111
- compilation, but that is not proof that Work configuration is localized in
112
- those languages. Add observed labels and a sanitized surface-profile fixture
113
- before claiming support for a new locale/profile combination.
109
+ English plus all 64 advertised non-English Settings languages were recaptured
110
+ against the current Chat and Work Power/Advanced selector graph on 2026-08-08.
111
+ That proves the reviewed labels for the observed account and rollout, not every
112
+ plan, region, workspace, or staged experiment. Keep capability discovery and
113
+ fail-closed shape checks even when a locale is present in the registry.
114
114
 
115
115
  Create a read-only draft from an already-open authorized ChatGPT tab:
116
116
 
117
117
  ```bash
118
118
  npm run capture:surface-profile -- \
119
119
  --id work-basic-de \
120
- --locale de-DE
120
+ --locale de-DE \
121
+ --experience work
121
122
  ```
122
123
 
123
124
  The command defaults region, plan, account, and workspace metadata to
@@ -127,6 +128,23 @@ identifiers, and excludes prompt/response/sidebar text. Review the draft under
127
128
  then move a sanitized fixture into `contracts/v1/fixtures/` and update the
128
129
  manifest/parity matrix.
129
130
 
131
+ To refresh every language in one visible-session sweep, use the existing
132
+ Settings language loop with structural Chat/Work capture enabled:
133
+
134
+ ```bash
135
+ npm run capture:intelligence-locales -- \
136
+ --auto-switch --all --capture-surfaces --if-missing open
137
+ ```
138
+
139
+ Review the JSONL evidence, then apply it only with the explicit review gate:
140
+
141
+ ```bash
142
+ npm run apply:intelligence-locales -- --in <capture.jsonl> --reviewed
143
+ ```
144
+
145
+ The applier keeps older locale contributions valid, adds only observed
146
+ non-English labels, and refuses incomplete surface captures instead of guessing.
147
+
130
148
  ## Adding a new language
131
149
 
132
150
  ### Step 1 — put a real ChatGPT session into the target language
@@ -186,11 +204,13 @@ Leave the canonical English first (it comes from `en.ts`), and leave the API key
186
204
  (`web_search`, `modeOptions.pro`, `modeOptions.high`, and the other semantic ids)
187
205
  unchanged.
188
206
 
189
- Newer ChatGPT rollouts may expose `Medium`, `High`, `Extra High`, and `Pro`
190
- under an `Intelligence` picker. Add localized equivalents only after observing
191
- those exact labels in the target locale. Put broad picker/opening labels in
192
- `modeLabels`, but put selectable labels under `modeOptions.<semantic-id>` so
193
- short labels such as `Pro` cannot match unrelated rows like `Move to project`.
207
+ Current ChatGPT rollouts may expose a basic Power control and nested Advanced
208
+ Model/Effort/Speed rows. Add localized equivalents only after observing those
209
+ exact labels in the target locale. Put legacy broad picker/opening labels in
210
+ `modeLabels`, selectable Chat effort labels under
211
+ `modeOptions.<semantic-id>`, and current selector labels under
212
+ `configurationAxes` / `configurationOptions` so short labels such as `Pro`
213
+ cannot match unrelated rows like `Move to project`.
194
214
 
195
215
  Then open [`src/dom/locale/index.ts`](../src/dom/locale/index.ts) and register the new
196
216
  locale:
@@ -274,8 +294,10 @@ Notes:
274
294
  fixture, or protocol change, because API keys and wire shapes are unchanged.
275
295
  - All consumer import paths (`from "../dom/locale-labels.js"`) are unchanged — the barrel
276
296
  `locale-labels.ts` re-exports everything from `locale/index.ts`.
277
- - `doctor({ check: ["localization"] })` verifies that the registry is populated and
278
- canonical English values are present. It also reports running-state localization coverage
297
+ - `doctor({ check: ["localization"] })` verifies that the legacy selector,
298
+ tool, mode-option, configuration-axis (including Power/Advanced), and
299
+ configuration-option registries are populated and canonical English values
300
+ are present. It also reports running-state localization coverage
279
301
  separately from the flattened selector list, because `stopControl` and
280
302
  `stoppedAssistant` require a live mid-generation capture. It does not yet prove full
281
303
  localized selector coverage; treat localized workflow failures as selector-wiring work
@@ -34,6 +34,10 @@ Incomplete response capture is also shared contract behavior. Python must preser
34
34
 
35
35
  For long-answer polling, Python forwards `response_content="metadata"` to the shared wire field `responseContent: "metadata"` on `messages.wait`. The TypeScript backend then omits assistant text from wait results and returns compact metadata such as `data.responseChars` and `data.responseSha256`; Python must preserve those fields without trying to reconstruct omitted content.
36
36
 
37
+ Python exposes the explicit stop primitive as `chatgpt.messages.stop(confirm_stop=True)`, mapping to `messages.stop` with `confirmStop: true`. The TypeScript backend owns visible-control selection, the single operation deadline, and inactive-state verification; Python adds no independent browser behavior.
38
+
39
+ Python preserves the backend's indeterminate Stop result exactly: `status == "timeout"`, blocker code `stop_generation_unverified`, and `resumable == False`. The browser request is terminated at its native deadline and cannot click later, but the click may already have taken effect. Python callers must inspect the visible state and must not retry automatically.
40
+
37
41
  Generated-image behavior stays owned by the TypeScript runtime. Python exposes
38
42
  the same backend commands through `chatgpt.artifacts.list_latest(...)`,
39
43
  `chatgpt.artifacts.wait(...)`, and `chatgpt.artifacts.download_latest(...)`.
@@ -63,6 +67,8 @@ Python does not reinterpret attachment paths. It sends the path string to the No
63
67
 
64
68
  Python exposes the backend-visible `files.preflight` command as `chatgpt.files.preflight(...)`. It returns the same `CommandResult` as TypeScript and can be decoded with `FilePreflightData` when callers want typed metadata. The command validates paths, readability, file-vs-directory status, size limits, duplicate basenames, duplicate resolved paths, zero-byte files, and extension-based MIME/category guesses without opening ChatGPT or reading file contents for MIME detection. Zero-byte files are blocked before browser interaction. Optional `include_hashes=True` / wire `includeHashes: true` adds SHA-256 metadata to `FilePreflightFile.sha256` for local diagnostics; file contents are never returned.
65
69
 
70
+ Python also preserves a post-handoff attachment uncertainty as `status == "partial"`, blocker code `attachment_outcome_indeterminate`, and `resumable == False`. Callers must inspect the current composer, must not submit, and must not automatically repeat the file handoff.
71
+
66
72
  ## Project Sources
67
73
 
68
74
  Python exposes the protocol-visible Project Sources commands through `chatgpt.projects.sources`:
@@ -211,8 +217,8 @@ python scripts/live_smoke.py --mode browser-bridge
211
217
  When the live backend is hosted inside the Codex Chrome plugin runtime, do not test bridge availability from a normal shell or an unbootstrapped Node REPL. First initialize the Chrome runtime:
212
218
 
213
219
  ```js
214
- const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/latest/scripts/browser-client.mjs");
215
- await setupBrowserRuntime({ globals: globalThis });
220
+ const { setupBrowserRuntime } = await import("/absolute/path/to/the/current/chrome/scripts/browser-client.mjs");
221
+ globalThis.agent = await setupBrowserRuntime();
216
222
  globalThis.browser = await agent.browsers.get("extension");
217
223
  ```
218
224
 
@@ -225,7 +231,7 @@ python scripts/live_smoke.py \
225
231
  --backend-command "node scripts/http_stdio_relay.mjs"
226
232
  ```
227
233
 
228
- Keep the bridge-hosted JS execution active while Python runs. If that JS execution returns first, the browser client no longer has an active execution context and operations can fail with `node_repl exec context not found`.
234
+ Create the backend server and wait on it in the **same** bridge-hosted JS execution. Starting the server in one Node REPL call and trying to keep it alive with a second call does not preserve the first call's browser execution context. Keep that single execution active while Python runs. If it returns first, the browser client no longer has an active execution context and operations can fail with `node_repl exec context not found`.
229
235
 
230
236
  This is the intended live test chain:
231
237
 
@@ -33,8 +33,8 @@ Use `chatgpt.explainBlocker(result)` or Python `explain_blocker(result)` when re
33
33
  Do not conclude that Chrome or the extension is broken from a plain shell result, or from checking `globalThis.agent` before the Chrome plugin runtime is initialized. For a true Codex Chrome-plugin live run, bootstrap the runtime first:
34
34
 
35
35
  ```js
36
- const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/latest/scripts/browser-client.mjs");
37
- await setupBrowserRuntime({ globals: globalThis });
36
+ const { setupBrowserRuntime } = await import("/absolute/path/to/the/current/chrome/scripts/browser-client.mjs");
37
+ globalThis.agent = await setupBrowserRuntime();
38
38
  globalThis.browser = await agent.browsers.get("extension");
39
39
  ```
40
40
 
@@ -49,7 +49,7 @@ python scripts/live_smoke.py \
49
49
  --backend-command "node scripts/http_stdio_relay.mjs"
50
50
  ```
51
51
 
52
- Keep the bridge-hosted JS execution active while Python runs. If that call returns first, browser operations can fail with `node_repl exec context not found`.
52
+ Create the backend server and wait on it in the **same** bridge-hosted JS execution. Starting it in one Node REPL call and waiting in another loses the first call's browser execution context. Keep that single call active while Python runs. If it returns first, browser operations can fail with `node_repl exec context not found`.
53
53
 
54
54
  ## `login_required`
55
55
 
@@ -80,6 +80,13 @@ loaded and the SDK refused to append. Pass `newTask: false` only when the user
80
80
  intends to continue that exact task. If a Work submission returns partial or
81
81
  timeout, use `work.status`, `work.wait`, or `work.readLatest`; do not resubmit.
82
82
 
83
+ On the current home page, inspect the `Select chat surface` Chat/Work radio
84
+ group before diagnosing Work as unavailable. Once a Work task is active, that
85
+ radio group can disappear; the compound model-plus-effort opener is the SDK's
86
+ continuation evidence, while the visible Work task chrome can corroborate a
87
+ manual diagnosis. `experience.open` returns to the home selector before
88
+ changing panes from an active task.
89
+
83
90
  ## Existing Tab Not Found Or Ambiguous
84
91
 
85
92
  When `existingTab` targeting cannot select one already-open tab, inspect `blocker.diagnostics.existingTab` or the rendered blocker explanation. Diagnostics are metadata-only: requested target, whether user-open tabs were available, candidate tab IDs, URLs, titles, conversation IDs, omitted candidate count, and mismatch reason. They must not include page text or chat content.
@@ -144,6 +151,14 @@ const final = await chatgpt.messages.readLatest({ format: "markdown" });
144
151
 
145
152
  Active generation may appear as a visible or accessible-name control such as `Stop answering`, `Stop generating`, or `Stop streaming`. Stopped generation may appear as `Stopped thinking`. Treat all of those as incomplete states.
146
153
 
154
+ If the caller explicitly supersedes a running request, use `messages.stop({ confirmStop: true })`. The command does nothing when generation is observably inactive and fails closed when it cannot inspect the state, uniquely activate the visible composer stop control, or verify the inactive postcondition. Do not use it as an automatic timeout-recovery retry mechanism.
155
+
156
+ If Stop returns `stop_generation_unverified` with `resumable: false`, the activation began but its outcome is unknown. The browser request has been terminated and cannot click later, although the click may already have taken effect. Inspect the visible generation state before any further action; never retry Stop automatically.
157
+
158
+ If attachment returns `attachment_outcome_indeterminate` with `resumable: false`, the native handoff began but the file's composer state is unknown. The browser request has been terminated and cannot attach later, although the file may already be present. Inspect the current composer, do not submit, and do not retry automatically.
159
+
160
+ The Codex Chrome file chooser does not expose its backing input. This is expected: the SDK proves the unique active-composer trigger before opening the chooser and uses any provider-specific backing-element accessor only as an additional cross-check. A missing chooser `element()` method by itself is not an upload failure.
161
+
147
162
  When the outer host tool-call ceiling is short, poll in bounded chunks instead of issuing a single very long wait:
148
163
 
149
164
  ```ts
@@ -161,7 +176,14 @@ When ChatGPT exposes previous/next response controls, `readLatest` and `copyLate
161
176
 
162
177
  ## Download Unavailable
163
178
 
164
- The command only downloads visible files with a download affordance. If no download control exists, ask ChatGPT to create or expose the file again.
179
+ The command only downloads visible files with a download affordance. Current
180
+ ChatGPT file answers may first expose a filename-labelled button; the SDK opens
181
+ that artifact preview and then uses its visible Download control. When the
182
+ expected filename is known, pass a case-insensitive regular expression such as
183
+ `filenamePattern: "^report\\.csv$"`. A `download_filename_not_found` blocker
184
+ means no visible assistant file matched; the SDK deliberately did not accept an
185
+ unrelated image fallback. If no download control exists, ask ChatGPT to create
186
+ or expose the file again.
165
187
 
166
188
  ## Redacted Reports
167
189