codex-chatgpt-control 0.1.0-alpha.1 → 0.3.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 (255) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +55 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +8 -0
  4. package/contracts/v1/fixtures/blocker-explanation-profiles.json +114 -0
  5. package/contracts/v1/fixtures/command-descriptors.json +224 -16
  6. package/contracts/v1/fixtures/doctor-bridge-upload.json +8 -0
  7. package/contracts/v1/fixtures/doctor-scenario-preflight.json +119 -0
  8. package/contracts/v1/fixtures/existing-tab-diagnostics-blocker.json +57 -0
  9. package/contracts/v1/fixtures/files-preflight-success.json +32 -0
  10. package/contracts/v1/fixtures/help-root.json +1 -1
  11. package/contracts/v1/fixtures/output-json-parse-success.json +17 -2
  12. package/contracts/v1/fixtures/project-sources-plan-add.json +71 -0
  13. package/contracts/v1/fixtures/read-latest-clipped.json +31 -0
  14. package/contracts/v1/fixtures/reports-create-redacted.json +15 -0
  15. package/contracts/v1/fixtures/responses-basic-success.json +11 -0
  16. package/contracts/v1/fixtures/run-basic-success.json +15 -0
  17. package/contracts/v1/fixtures/run-browser-bridge-blocker.json +2 -2
  18. package/contracts/v1/fixtures/run-timeout-partial.json +2 -2
  19. package/contracts/v1/fixtures/runner-budget-blocker.json +1 -1
  20. package/contracts/v1/fixtures/stream-blocked.ndjson +1 -1
  21. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
  22. package/contracts/v1/manifest.json +30 -0
  23. package/contracts/v1/parity-suite.json +80 -7
  24. package/contracts/v1/schemas/backend-request.schema.json +3 -0
  25. package/contracts/v1/schemas/command-result.schema.json +48 -0
  26. package/contracts/v1/schemas/response.schema.json +28 -0
  27. package/contracts/v1/schemas/run-result.schema.json +84 -0
  28. package/contracts/v1/surface-drift-policy.json +210 -0
  29. package/dist/codex-chatgpt-control-backend.mjs +7478 -1789
  30. package/dist/codex-chatgpt-control.bundle.mjs +8023 -2266
  31. package/dist/src/backend/client.d.ts +6 -1
  32. package/dist/src/backend/client.js +5 -0
  33. package/dist/src/backend/protocol.d.ts +1 -1
  34. package/dist/src/backend/protocol.js +8 -0
  35. package/dist/src/backend/session.js +16 -0
  36. package/dist/src/browser/attach.js +128 -20
  37. package/dist/src/browser/clipboard.d.ts +11 -0
  38. package/dist/src/browser/clipboard.js +34 -7
  39. package/dist/src/browser/downloads.js +2 -1
  40. package/dist/src/browser/page-state.js +18 -5
  41. package/dist/src/client.d.ts +17 -1
  42. package/dist/src/client.js +89 -9
  43. package/dist/src/commands/artifacts.d.ts +5 -0
  44. package/dist/src/commands/artifacts.js +432 -0
  45. package/dist/src/commands/context.js +8 -3
  46. package/dist/src/commands/conversation.d.ts +15 -0
  47. package/dist/src/commands/conversation.js +44 -0
  48. package/dist/src/commands/deadline.d.ts +8 -0
  49. package/dist/src/commands/deadline.js +14 -0
  50. package/dist/src/commands/doctor.d.ts +10 -2
  51. package/dist/src/commands/doctor.js +249 -14
  52. package/dist/src/commands/files.d.ts +2 -1
  53. package/dist/src/commands/files.js +406 -18
  54. package/dist/src/commands/messages.d.ts +2 -1
  55. package/dist/src/commands/messages.js +430 -98
  56. package/dist/src/commands/modes.d.ts +4 -1
  57. package/dist/src/commands/modes.js +404 -37
  58. package/dist/src/commands/probes.d.ts +15 -0
  59. package/dist/src/commands/probes.js +64 -0
  60. package/dist/src/commands/project-sources.d.ts +10 -0
  61. package/dist/src/commands/project-sources.js +643 -0
  62. package/dist/src/commands/registry.js +108 -14
  63. package/dist/src/commands/reports.d.ts +9 -0
  64. package/dist/src/commands/reports.js +84 -7
  65. package/dist/src/commands/response-actions.js +33 -0
  66. package/dist/src/commands/sequence.js +14 -0
  67. package/dist/src/commands/threads.js +4 -25
  68. package/dist/src/commands/timeouts.d.ts +2 -0
  69. package/dist/src/commands/timeouts.js +18 -0
  70. package/dist/src/diagnostics/blockers.d.ts +54 -0
  71. package/dist/src/diagnostics/blockers.js +299 -0
  72. package/dist/src/dom/artifacts.d.ts +7 -0
  73. package/dist/src/dom/artifacts.js +198 -0
  74. package/dist/src/dom/generation-state.d.ts +15 -0
  75. package/dist/src/dom/generation-state.js +109 -0
  76. package/dist/src/dom/label-match.d.ts +3 -0
  77. package/dist/src/dom/label-match.js +25 -0
  78. package/dist/src/dom/locale/am.d.ts +34 -0
  79. package/dist/src/dom/locale/am.js +34 -0
  80. package/dist/src/dom/locale/ar.d.ts +35 -0
  81. package/dist/src/dom/locale/ar.js +35 -0
  82. package/dist/src/dom/locale/bg.d.ts +35 -0
  83. package/dist/src/dom/locale/bg.js +35 -0
  84. package/dist/src/dom/locale/bn.d.ts +35 -0
  85. package/dist/src/dom/locale/bn.js +35 -0
  86. package/dist/src/dom/locale/bs.d.ts +34 -0
  87. package/dist/src/dom/locale/bs.js +34 -0
  88. package/dist/src/dom/locale/ca.d.ts +34 -0
  89. package/dist/src/dom/locale/ca.js +34 -0
  90. package/dist/src/dom/locale/cs.d.ts +34 -0
  91. package/dist/src/dom/locale/cs.js +34 -0
  92. package/dist/src/dom/locale/da.d.ts +33 -0
  93. package/dist/src/dom/locale/da.js +33 -0
  94. package/dist/src/dom/locale/de.d.ts +36 -0
  95. package/dist/src/dom/locale/de.js +36 -0
  96. package/dist/src/dom/locale/el.d.ts +34 -0
  97. package/dist/src/dom/locale/el.js +34 -0
  98. package/dist/src/dom/locale/en.d.ts +64 -0
  99. package/dist/src/dom/locale/en.js +71 -0
  100. package/dist/src/dom/locale/es-419.d.ts +34 -0
  101. package/dist/src/dom/locale/es-419.js +34 -0
  102. package/dist/src/dom/locale/es-ES.d.ts +34 -0
  103. package/dist/src/dom/locale/es-ES.js +34 -0
  104. package/dist/src/dom/locale/et.d.ts +34 -0
  105. package/dist/src/dom/locale/et.js +34 -0
  106. package/dist/src/dom/locale/fa.d.ts +35 -0
  107. package/dist/src/dom/locale/fa.js +35 -0
  108. package/dist/src/dom/locale/fi.d.ts +34 -0
  109. package/dist/src/dom/locale/fi.js +34 -0
  110. package/dist/src/dom/locale/fr-CA.d.ts +35 -0
  111. package/dist/src/dom/locale/fr-CA.js +35 -0
  112. package/dist/src/dom/locale/fr-FR.d.ts +35 -0
  113. package/dist/src/dom/locale/fr-FR.js +35 -0
  114. package/dist/src/dom/locale/gu.d.ts +34 -0
  115. package/dist/src/dom/locale/gu.js +34 -0
  116. package/dist/src/dom/locale/hi.d.ts +34 -0
  117. package/dist/src/dom/locale/hi.js +34 -0
  118. package/dist/src/dom/locale/hr.d.ts +33 -0
  119. package/dist/src/dom/locale/hr.js +33 -0
  120. package/dist/src/dom/locale/hu.d.ts +34 -0
  121. package/dist/src/dom/locale/hu.js +34 -0
  122. package/dist/src/dom/locale/hy.d.ts +35 -0
  123. package/dist/src/dom/locale/hy.js +35 -0
  124. package/dist/src/dom/locale/id.d.ts +34 -0
  125. package/dist/src/dom/locale/id.js +34 -0
  126. package/dist/src/dom/locale/index.d.ts +83 -0
  127. package/dist/src/dom/locale/index.js +239 -0
  128. package/dist/src/dom/locale/is.d.ts +34 -0
  129. package/dist/src/dom/locale/is.js +34 -0
  130. package/dist/src/dom/locale/it.d.ts +33 -0
  131. package/dist/src/dom/locale/it.js +33 -0
  132. package/dist/src/dom/locale/ja.d.ts +33 -0
  133. package/dist/src/dom/locale/ja.js +33 -0
  134. package/dist/src/dom/locale/ka.d.ts +34 -0
  135. package/dist/src/dom/locale/ka.js +34 -0
  136. package/dist/src/dom/locale/kk.d.ts +34 -0
  137. package/dist/src/dom/locale/kk.js +34 -0
  138. package/dist/src/dom/locale/kn.d.ts +35 -0
  139. package/dist/src/dom/locale/kn.js +35 -0
  140. package/dist/src/dom/locale/ko.d.ts +34 -0
  141. package/dist/src/dom/locale/ko.js +34 -0
  142. package/dist/src/dom/locale/lt.d.ts +35 -0
  143. package/dist/src/dom/locale/lt.js +35 -0
  144. package/dist/src/dom/locale/lv.d.ts +34 -0
  145. package/dist/src/dom/locale/lv.js +34 -0
  146. package/dist/src/dom/locale/mk.d.ts +33 -0
  147. package/dist/src/dom/locale/mk.js +33 -0
  148. package/dist/src/dom/locale/ml.d.ts +35 -0
  149. package/dist/src/dom/locale/ml.js +35 -0
  150. package/dist/src/dom/locale/mn.d.ts +35 -0
  151. package/dist/src/dom/locale/mn.js +35 -0
  152. package/dist/src/dom/locale/mr.d.ts +35 -0
  153. package/dist/src/dom/locale/mr.js +35 -0
  154. package/dist/src/dom/locale/ms.d.ts +34 -0
  155. package/dist/src/dom/locale/ms.js +34 -0
  156. package/dist/src/dom/locale/my.d.ts +34 -0
  157. package/dist/src/dom/locale/my.js +34 -0
  158. package/dist/src/dom/locale/nb.d.ts +34 -0
  159. package/dist/src/dom/locale/nb.js +34 -0
  160. package/dist/src/dom/locale/nl.d.ts +34 -0
  161. package/dist/src/dom/locale/nl.js +34 -0
  162. package/dist/src/dom/locale/pa.d.ts +35 -0
  163. package/dist/src/dom/locale/pa.js +35 -0
  164. package/dist/src/dom/locale/pl.d.ts +34 -0
  165. package/dist/src/dom/locale/pl.js +34 -0
  166. package/dist/src/dom/locale/pt-BR.d.ts +34 -0
  167. package/dist/src/dom/locale/pt-BR.js +34 -0
  168. package/dist/src/dom/locale/pt-PT.d.ts +34 -0
  169. package/dist/src/dom/locale/pt-PT.js +34 -0
  170. package/dist/src/dom/locale/ro.d.ts +33 -0
  171. package/dist/src/dom/locale/ro.js +33 -0
  172. package/dist/src/dom/locale/ru.d.ts +31 -0
  173. package/dist/src/dom/locale/ru.js +31 -0
  174. package/dist/src/dom/locale/sk.d.ts +34 -0
  175. package/dist/src/dom/locale/sk.js +34 -0
  176. package/dist/src/dom/locale/sl.d.ts +34 -0
  177. package/dist/src/dom/locale/sl.js +34 -0
  178. package/dist/src/dom/locale/so.d.ts +34 -0
  179. package/dist/src/dom/locale/so.js +34 -0
  180. package/dist/src/dom/locale/sq.d.ts +34 -0
  181. package/dist/src/dom/locale/sq.js +34 -0
  182. package/dist/src/dom/locale/sr.d.ts +31 -0
  183. package/dist/src/dom/locale/sr.js +31 -0
  184. package/dist/src/dom/locale/sv.d.ts +34 -0
  185. package/dist/src/dom/locale/sv.js +34 -0
  186. package/dist/src/dom/locale/sw.d.ts +34 -0
  187. package/dist/src/dom/locale/sw.js +34 -0
  188. package/dist/src/dom/locale/ta.d.ts +35 -0
  189. package/dist/src/dom/locale/ta.js +35 -0
  190. package/dist/src/dom/locale/te.d.ts +35 -0
  191. package/dist/src/dom/locale/te.js +35 -0
  192. package/dist/src/dom/locale/th.d.ts +34 -0
  193. package/dist/src/dom/locale/th.js +34 -0
  194. package/dist/src/dom/locale/tl.d.ts +28 -0
  195. package/dist/src/dom/locale/tl.js +28 -0
  196. package/dist/src/dom/locale/tr.d.ts +34 -0
  197. package/dist/src/dom/locale/tr.js +34 -0
  198. package/dist/src/dom/locale/types.d.ts +52 -0
  199. package/dist/src/dom/locale/types.js +1 -0
  200. package/dist/src/dom/locale/uk.d.ts +34 -0
  201. package/dist/src/dom/locale/uk.js +34 -0
  202. package/dist/src/dom/locale/ur.d.ts +34 -0
  203. package/dist/src/dom/locale/ur.js +34 -0
  204. package/dist/src/dom/locale/vi.d.ts +34 -0
  205. package/dist/src/dom/locale/vi.js +34 -0
  206. package/dist/src/dom/locale/zh-HK.d.ts +35 -0
  207. package/dist/src/dom/locale/zh-HK.js +35 -0
  208. package/dist/src/dom/locale/zh-Hans.d.ts +35 -0
  209. package/dist/src/dom/locale/zh-Hans.js +35 -0
  210. package/dist/src/dom/locale/zh-TW.d.ts +36 -0
  211. package/dist/src/dom/locale/zh-TW.js +36 -0
  212. package/dist/src/dom/locale-labels.d.ts +26 -0
  213. package/dist/src/dom/locale-labels.js +26 -0
  214. package/dist/src/dom/menus.d.ts +6 -0
  215. package/dist/src/dom/menus.js +47 -10
  216. package/dist/src/dom/message-format.d.ts +2 -1
  217. package/dist/src/dom/message-format.js +67 -10
  218. package/dist/src/dom/messages.d.ts +2 -1
  219. package/dist/src/dom/messages.js +2 -4
  220. package/dist/src/dom/selectors.d.ts +1 -0
  221. package/dist/src/dom/selectors.js +42 -15
  222. package/dist/src/dom/wait-snapshot.d.ts +43 -0
  223. package/dist/src/dom/wait-snapshot.js +127 -0
  224. package/dist/src/index.d.ts +5 -0
  225. package/dist/src/index.js +5 -0
  226. package/dist/src/platform/local-paths.d.ts +4 -0
  227. package/dist/src/platform/local-paths.js +20 -0
  228. package/dist/src/runner/interruptions.js +15 -4
  229. package/dist/src/runner/responses.js +18 -1
  230. package/dist/src/runner/result.js +15 -0
  231. package/dist/src/runner/resume.js +1 -0
  232. package/dist/src/runner/types.d.ts +3 -0
  233. package/dist/src/safety/risk.d.ts +8 -0
  234. package/dist/src/safety/risk.js +8 -0
  235. package/dist/src/safety/untrusted-output.d.ts +73 -0
  236. package/dist/src/safety/untrusted-output.js +199 -0
  237. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +1 -0
  238. package/dist/src/scripts/apply-intelligence-locale-captures.js +270 -0
  239. package/dist/src/scripts/capture-intelligence-locales.d.ts +25 -0
  240. package/dist/src/scripts/capture-intelligence-locales.js +669 -0
  241. package/dist/src/scripts/check-surface-drift.d.ts +1 -0
  242. package/dist/src/scripts/check-surface-drift.js +11 -0
  243. package/dist/src/scripts/live-smoke/scenarios.js +84 -0
  244. package/dist/src/scripts/locale-capture/language-coverage.d.ts +10 -0
  245. package/dist/src/scripts/locale-capture/language-coverage.js +39 -0
  246. package/dist/src/testing/surface-drift.d.ts +66 -0
  247. package/dist/src/testing/surface-drift.js +363 -0
  248. package/dist/src/types.d.ts +229 -2
  249. package/package.json +3 -2
  250. package/references/backend-protocol.md +96 -2
  251. package/references/language-coverage.md +142 -0
  252. package/references/localization.md +257 -0
  253. package/references/python-parity.md +67 -3
  254. package/references/safety.md +2 -0
  255. package/references/troubleshooting.md +83 -2
@@ -1,5 +1,5 @@
1
1
  export type CommandStatus = "ok" | "partial" | "timeout" | "blocked" | "needs_confirmation" | "not_found" | "unsupported" | "error";
2
- export type BlockerKind = "browser_bridge_unavailable" | "login_required" | "captcha" | "rate_limit" | "modal" | "permission" | "confirmation" | "selector_drift" | "download_unavailable" | "upload_failed" | "not_found" | "unknown";
2
+ export type BlockerKind = "browser_bridge_unavailable" | "login_required" | "captcha" | "rate_limit" | "modal" | "permission" | "confirmation" | "selector_drift" | "artifact_unavailable" | "artifact_selector_drift" | "artifact_download_unavailable" | "download_unavailable" | "upload_failed" | "not_found" | "unknown";
3
3
  export type CommandContext = {
4
4
  url?: string;
5
5
  conversationId?: string;
@@ -10,6 +10,32 @@ export type CommandContext = {
10
10
  tabId?: string;
11
11
  timestamp: string;
12
12
  };
13
+ export type ExistingTabMismatchReason = "no_candidate" | "multiple_candidates" | "non_chatgpt_tab" | "conversation_id_mismatch" | "url_mismatch" | "title_mismatch" | "selected_tab_unavailable" | "explicit_tab_id_not_open" | "user_open_tabs_unavailable";
14
+ export type ExistingTabDiagnosticTarget = {
15
+ type: string;
16
+ host?: string;
17
+ tabId?: string;
18
+ conversationId?: string;
19
+ url?: string;
20
+ title?: string;
21
+ exact?: boolean;
22
+ };
23
+ export type ExistingTabDiagnosticCandidate = {
24
+ id: string;
25
+ url?: string;
26
+ title?: string;
27
+ conversationId?: string;
28
+ lastOpened?: string;
29
+ tabGroup?: string;
30
+ };
31
+ export type ExistingTabDiagnostics = {
32
+ requestedTarget: ExistingTabDiagnosticTarget;
33
+ userOpenTabsAvailable: boolean;
34
+ chatgptTabCount: number;
35
+ mismatchReason: ExistingTabMismatchReason;
36
+ candidateTabs: ExistingTabDiagnosticCandidate[];
37
+ omittedCandidateCount?: number;
38
+ };
13
39
  export type CommandResult<T = unknown> = {
14
40
  ok: boolean;
15
41
  status: CommandStatus;
@@ -37,6 +63,9 @@ export type CommandResult<T = unknown> = {
37
63
  label: string;
38
64
  role?: string;
39
65
  }>;
66
+ diagnostics?: {
67
+ existingTab?: ExistingTabDiagnostics;
68
+ };
40
69
  resumable?: boolean;
41
70
  };
42
71
  context: CommandContext;
@@ -154,10 +183,14 @@ export type WaitArgs = {
154
183
  stableMs?: number;
155
184
  pollMs?: number;
156
185
  mode?: "normal" | "deep_research";
186
+ responseContent?: "include" | "metadata";
157
187
  };
158
188
  export type WaitData = {
159
189
  complete: boolean;
160
190
  responseText?: string;
191
+ responseChars?: number;
192
+ responseSha256?: string;
193
+ responseContent?: "include" | "metadata";
161
194
  assistantTurnCount: number;
162
195
  elapsedMs: number;
163
196
  };
@@ -176,6 +209,11 @@ export type ResponseTable = {
176
209
  };
177
210
  export type ResponseCaptureSource = "semantic_dom" | "clipboard";
178
211
  export type ResponseCaptureFidelity = "clipboard_markdown" | "semantic_markdown" | "visible_text" | "normalized_text" | "html" | "blocks" | "all";
212
+ export type ResponseCaptureLimit = {
213
+ maxChars: number;
214
+ originalChars: number;
215
+ clipped: boolean;
216
+ };
179
217
  export type ResponseBranchState = {
180
218
  current?: number;
181
219
  total?: number;
@@ -225,6 +263,7 @@ export type ReadLatestData = {
225
263
  format: Exclude<ResponseFormat, "text">;
226
264
  source?: ResponseCaptureSource;
227
265
  fidelity?: ResponseCaptureFidelity;
266
+ captureLimit?: ResponseCaptureLimit;
228
267
  warnings?: string[];
229
268
  markdown?: string;
230
269
  visibleText?: string;
@@ -256,14 +295,101 @@ export type AskReadData = {
256
295
  export type AttachFilesArgs = {
257
296
  paths: string[];
258
297
  timeoutMs?: number;
298
+ includeDiagnostics?: boolean;
299
+ includeHashes?: boolean;
259
300
  };
260
301
  export type AttachedFile = {
261
302
  path: string;
262
303
  name: string;
263
304
  bytes: number;
264
305
  };
306
+ export type FileCategory = "text" | "document" | "spreadsheet" | "data" | "image" | "audio" | "video" | "archive" | "unknown";
307
+ export type FilePreflightArgs = {
308
+ paths: string[];
309
+ maxBytesPerFile?: number;
310
+ maxTotalBytes?: number;
311
+ includeHashes?: boolean;
312
+ };
313
+ export type FilePreflightFile = AttachedFile & {
314
+ extension: string;
315
+ mimeType: string;
316
+ category: FileCategory;
317
+ sha256?: string;
318
+ };
319
+ export type FilePreflightData = {
320
+ files: FilePreflightFile[];
321
+ totalBytes: number;
322
+ };
265
323
  export type AttachFilesData = {
266
324
  files: AttachedFile[];
325
+ diagnostics?: FileUploadDiagnostics;
326
+ };
327
+ export type BrowserInputFileDiagnostic = {
328
+ name: string;
329
+ size: number;
330
+ type?: string;
331
+ lastModified?: number;
332
+ };
333
+ export type BrowserInputDiagnostic = {
334
+ files: BrowserInputFileDiagnostic[];
335
+ };
336
+ export type FileUploadDiagnostics = {
337
+ preflight: FilePreflightData;
338
+ browserInput?: BrowserInputDiagnostic;
339
+ };
340
+ export type ProjectSourceStatus = "ready" | "processing" | "failed" | "unknown";
341
+ export type ProjectSource = {
342
+ name: string;
343
+ status: ProjectSourceStatus;
344
+ };
345
+ export type ProjectSourcesUrl = {
346
+ projectId: string;
347
+ projectSlug?: string;
348
+ url: string;
349
+ };
350
+ export type ProjectSourcesListArgs = {
351
+ projectUrl: string;
352
+ existingTab?: boolean | ExistingTabPolicy;
353
+ preferExistingTab?: boolean;
354
+ timeoutMs?: number;
355
+ };
356
+ export type ProjectSourcesListData = ProjectSourcesUrl & {
357
+ sources: ProjectSource[];
358
+ };
359
+ export type ProjectSourcePlanFile = FilePreflightFile & {
360
+ displayPath: string;
361
+ };
362
+ export type ProjectSourceUploadBatch = {
363
+ index: number;
364
+ files: ProjectSourcePlanFile[];
365
+ totalBytes: number;
366
+ };
367
+ export type ProjectSourcesPlanAddArgs = {
368
+ projectUrl: string;
369
+ files: string[];
370
+ batchSize?: number;
371
+ maxBytesPerFile?: number;
372
+ maxTotalBytes?: number;
373
+ };
374
+ export type ProjectSourcesAddPlanData = ProjectSourcesUrl & {
375
+ projectUrl: string;
376
+ operation: "append_add";
377
+ dryRun: true;
378
+ files: ProjectSourcePlanFile[];
379
+ batches: ProjectSourceUploadBatch[];
380
+ totalBytes: number;
381
+ };
382
+ export type ProjectSourcesAddArgs = ProjectSourcesPlanAddArgs & {
383
+ confirmMutation?: boolean;
384
+ existingTab?: boolean | ExistingTabPolicy;
385
+ preferExistingTab?: boolean;
386
+ timeoutMs?: number;
387
+ };
388
+ export type ProjectSourcesAddData = Omit<ProjectSourcesAddPlanData, "dryRun"> & {
389
+ dryRun: false;
390
+ before: ProjectSource[];
391
+ after: ProjectSource[];
392
+ added: ProjectSource[];
267
393
  };
268
394
  export type DownloadLatestArgs = {
269
395
  destDir: string;
@@ -278,6 +404,47 @@ export type DownloadedFile = {
278
404
  suggestedFilename?: string;
279
405
  bytes: number;
280
406
  };
407
+ export type ArtifactKind = "image";
408
+ export type GeneratedArtifact = {
409
+ kind: ArtifactKind;
410
+ index: number;
411
+ visible: boolean;
412
+ width?: number;
413
+ height?: number;
414
+ alt?: string;
415
+ ariaLabel?: string;
416
+ src?: string;
417
+ turnId?: string;
418
+ downloadAvailable: boolean;
419
+ selectorProvenance: string;
420
+ };
421
+ export type ListArtifactsArgs = {
422
+ kind?: ArtifactKind;
423
+ max?: number;
424
+ timeoutMs?: number;
425
+ };
426
+ export type ArtifactListData = {
427
+ count: number;
428
+ artifacts: GeneratedArtifact[];
429
+ latest?: GeneratedArtifact;
430
+ };
431
+ export type ArtifactWaitArgs = ListArtifactsArgs & {
432
+ afterArtifactCount?: number;
433
+ requireDownload?: boolean;
434
+ timeoutMs?: number;
435
+ stableMs?: number;
436
+ pollMs?: number;
437
+ };
438
+ export type ArtifactWaitData = {
439
+ complete: boolean;
440
+ count: number;
441
+ latest?: GeneratedArtifact;
442
+ elapsedMs: number;
443
+ };
444
+ export type ArtifactDownloadArgs = DownloadLatestArgs & {
445
+ kind?: ArtifactKind;
446
+ prefer?: "download_control" | "visible_image_source";
447
+ };
281
448
  export type CopyResponseArgs = {
282
449
  which?: "latest" | {
283
450
  assistantIndex: number;
@@ -291,6 +458,7 @@ export type CopiedResponse = {
291
458
  source: "clipboard" | "dom";
292
459
  format: Exclude<ResponseFormat, "text">;
293
460
  fidelity?: ResponseCaptureFidelity;
461
+ captureLimit?: ResponseCaptureLimit;
294
462
  warnings?: string[];
295
463
  markdown?: string;
296
464
  visibleText?: string;
@@ -309,8 +477,17 @@ export type CopiedResponse = {
309
477
  export type SetModeArgs = {
310
478
  model?: string;
311
479
  effort?: string;
480
+ intelligence?: string;
481
+ modelVersion?: string;
482
+ version?: string;
483
+ timeoutMs?: number;
484
+ };
485
+ export type GetModeArgs = {
312
486
  timeoutMs?: number;
313
487
  };
488
+ export type GetModeData = {
489
+ modes: string[];
490
+ };
314
491
  export type SelectToolArgs = {
315
492
  tool: "web_search" | "deep_research" | "create_image" | string;
316
493
  timeoutMs?: number;
@@ -355,6 +532,18 @@ export type SequenceStep = {
355
532
  id: string;
356
533
  command: "messages.waitAndRead";
357
534
  args: WaitAndReadArgs;
535
+ } | {
536
+ id: string;
537
+ command: "artifacts.listLatest";
538
+ args?: ListArtifactsArgs;
539
+ } | {
540
+ id: string;
541
+ command: "artifacts.wait";
542
+ args?: ArtifactWaitArgs;
543
+ } | {
544
+ id: string;
545
+ command: "artifacts.downloadLatest";
546
+ args: ArtifactDownloadArgs;
358
547
  } | {
359
548
  id: string;
360
549
  command: "files.attach";
@@ -363,6 +552,18 @@ export type SequenceStep = {
363
552
  id: string;
364
553
  command: "files.downloadLatest";
365
554
  args: DownloadLatestArgs;
555
+ } | {
556
+ id: string;
557
+ command: "projects.sources.list";
558
+ args: ProjectSourcesListArgs;
559
+ } | {
560
+ id: string;
561
+ command: "projects.sources.planAdd";
562
+ args: ProjectSourcesPlanAddArgs;
563
+ } | {
564
+ id: string;
565
+ command: "projects.sources.add";
566
+ args: ProjectSourcesAddArgs;
366
567
  } | {
367
568
  id: string;
368
569
  command: "response.copy";
@@ -411,10 +612,13 @@ export type BrowserLike = {
411
612
  };
412
613
  tabs?: {
413
614
  create?: (url: string) => Promise<PageLike> | PageLike;
414
- new?: (url: string) => Promise<PageLike> | PageLike;
615
+ new?: (url?: string) => Promise<PageLike> | PageLike;
415
616
  selected?: () => Promise<PageLike | undefined> | PageLike | undefined;
416
617
  list?: () => Promise<PageLike[]> | PageLike[];
417
618
  get?: (id: string) => Promise<PageLike> | PageLike;
619
+ finalize?: (options: {
620
+ keep?: unknown[];
621
+ }) => Promise<void>;
418
622
  };
419
623
  newPage?: () => Promise<PageLike> | PageLike;
420
624
  };
@@ -462,10 +666,33 @@ export type PageLike = {
462
666
  keyboard?: {
463
667
  press?: (key: string) => Promise<void>;
464
668
  };
669
+ mouse?: {
670
+ move?: (x: number, y: number) => Promise<void> | void;
671
+ click?: (x: number, y: number) => Promise<void> | void;
672
+ };
673
+ cua?: {
674
+ move?: (options: {
675
+ x: number;
676
+ y: number;
677
+ }) => Promise<void> | void;
678
+ click?: (options: {
679
+ x: number;
680
+ y: number;
681
+ button?: number;
682
+ }) => Promise<void> | void;
683
+ };
465
684
  waitForTimeout?: (ms: number) => Promise<void>;
466
685
  waitForEvent?: (event: string, optionsOrCallback?: WaitForEventOptions | unknown) => Promise<unknown>;
467
686
  evaluate?: <T, A = unknown>(fn: (arg: A) => T | Promise<T>, arg?: A) => Promise<T>;
468
687
  content?: () => Promise<string>;
688
+ close?: () => Promise<void>;
689
+ capabilities?: {
690
+ get?: (id: string) => Promise<unknown> | unknown;
691
+ };
692
+ playwright?: {
693
+ waitForTimeout?: (ms: number) => Promise<void>;
694
+ [key: string]: unknown;
695
+ };
469
696
  };
470
697
  export type AskHelperArgs = AskArgs & {
471
698
  thread?: ThreadTarget;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-chatgpt-control",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.3.0-alpha.1",
4
4
  "description": "Unofficial SDK for Codex agents controlling visible ChatGPT web sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,6 +27,7 @@
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
29
  "contract:validate": "node scripts/validate-contract.mjs",
30
+ "docs:drift": "tsx src/scripts/check-surface-drift.ts",
30
31
  "parity:fixtures": "node scripts/check-parity-fixtures.mjs",
31
32
  "parity:suite": "tsx src/scripts/check-parity-suite.ts",
32
33
  "test:backend-conformance": "vitest run tests/unit/backend-conformance.test.ts",
@@ -73,7 +74,7 @@
73
74
  "devDependencies": {
74
75
  "@types/node": "^24.13.1",
75
76
  "ajv": "^8.17.1",
76
- "esbuild": "^0.28.0",
77
+ "esbuild": "^0.28.1",
77
78
  "tsx": "^4.20.0",
78
79
  "typescript": "^5.8.0",
79
80
  "vitest": "^4.1.8"
@@ -67,6 +67,8 @@ Current protocol error codes are:
67
67
 
68
68
  Browser-control blockers are not protocol errors. They are normal command or runner results with `status: "blocked"`, `status: "partial"`, or `status: "needs_confirmation"` plus blocker/interruption details.
69
69
 
70
+ `status: "partial"` is the required result for incomplete response capture. A partial result may still include `output_text` and `data.responseText`, but consumers must treat that text as incomplete until a later wait confirms completion. Common causes are wait timeout after partial assistant text, active generation controls such as `Stop answering`, stopped-generation markers such as `Stopped thinking`, or a read fallback after the wait step could not confirm completion. For status-only polling, `messages.wait` accepts `responseContent: "metadata"`; partial and completed wait results then omit assistant text and instead return compact metadata such as `data.responseChars` and `data.responseSha256`. Intentional capture clipping uses `data.captureLimit` plus warnings; it is separate from ChatGPT generation length.
71
+
70
72
  ## Streaming
71
73
 
72
74
  Streaming commands emit backend event lines until `completed` or `error`.
@@ -107,9 +109,56 @@ The backend must support:
107
109
  - runner: `runner.run`, `runner.plan`, `runner.stream`
108
110
  - Responses adapter: `responses.create`
109
111
  - workflows: `ask`, `askInThread`, `askWithFiles`, `askAndDownload`, `runMessages`, `openThread`, `runPlan`
112
+ - diagnostics: `doctor`
110
113
  - reports: `createReport`, `reports.create`, `reports.redact`, `reports.summarize`
111
114
  - command discovery: `commands`, `describe`, `help`
112
- - primitives: `session.bootstrap`, `threads.*`, `messages.*`, `files.*`, `modes.set`, `tools.select`, `response.copy`
115
+ - primitives: `session.bootstrap`, `threads.*`, `messages.*`, `artifacts.*`, `files.preflight`, `files.attach`, `files.downloadLatest`, `projects.sources.list`, `projects.sources.planAdd`, `projects.sources.add`, `modes.set`, `modes.get`, `tools.select`, `response.copy`
116
+
117
+ `doctor` returns a normal `CommandResult` whose `data.checks` map is extensible. Scenario checks such as `existing_tab`, `artifacts`, `file_preflight`, `localization`, and `reports` may add optional `code`, `blockerKind`, `nextCommand`, and JSON `details` fields to individual check entries while preserving the existing `status`, `message`, and `remediation` fields.
118
+
119
+ ## Host-Local Attachment Paths
120
+
121
+ Attachment paths are interpreted on the machine running the Node backend. Use an absolute path in that host operating system's native form. On macOS/Linux/WSL, use paths such as `/example/user/file.pdf`, `/home/you/file.pdf`, or `/mnt/c/example/user/file.pdf`. On Windows backend hosts, use fully qualified paths such as `C:\Users\you\file.pdf` or UNC paths such as `\\server\share\file.pdf`. Drive-relative paths like `C:Users\you\file.pdf`, root-relative paths like `\tmp\file.pdf`, and Windows-looking paths sent to a POSIX backend are rejected before filesystem access.
122
+
123
+ Use `files.preflight` for non-mutating local validation before browser upload workflows. It validates absolute paths, existence, readability, file-vs-directory status, configurable per-file and total byte limits, duplicate basenames, duplicate resolved paths, zero-byte files, and extension-based MIME/category guesses. Zero-byte files are blocked before browser interaction because ChatGPT rejects empty attachments. By default the command does not open ChatGPT, perform a live upload, read file contents for MIME detection, or return file-content fingerprints. Callers may pass `includeHashes: true` to include SHA-256 metadata for local diagnostics; file contents are never returned. `askWithFiles` and `files.attach` run the same preflight before upload attempts so obvious local file failures stop before browser interaction.
124
+
125
+ `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.
126
+
127
+ ## Project Sources
128
+
129
+ Project Sources V1 is a narrow visible-UI surface for ChatGPT Project URLs such as `https://chatgpt.com/g/g-p-.../project`.
130
+
131
+ - `projects.sources.list` opens or claims the Project Sources UI and returns source names plus coarse statuses only.
132
+ - `projects.sources.planAdd` validates explicit local file paths with the same metadata-only preflight used by `files.preflight`, batches the upload plan, and does not open ChatGPT.
133
+ - `projects.sources.add` is append-only and requires `confirmMutation: true`; without it, the command returns a `needs_confirmation` blocker with the dry-run plan.
134
+
135
+ The implementation normalizes nested project chat URLs back to the project page before operating. It does not delete, replace, sync repository trees, read cookies/storage/auth headers, call private ChatGPT endpoints, or read source file contents while planning. Confirmed upload uses visible browser file chooser primitives and diffs before/after source names to report added sources.
136
+
137
+ ## Generated Artifacts
138
+
139
+ Generated images are represented as visible artifacts, not assistant text. A
140
+ ChatGPT image-only result can be complete even when `messages.readLatest` returns
141
+ `not_found` and `assistantTurnCount` is `0`.
142
+
143
+ Use the artifact primitives for this surface:
144
+
145
+ - `artifacts.listLatest` detects visible generated artifacts.
146
+ - `artifacts.wait` waits for a generated artifact to appear and stabilize.
147
+ - `artifacts.downloadLatest` downloads via a visible artifact affordance, or
148
+ saves a visible image source when no browser download event fires.
149
+
150
+ When a claimed user-open ChatGPT tab stalls bridge page inspection, artifact
151
+ commands may recover by opening the same saved `https://chatgpt.com/c/...`
152
+ conversation in a temporary bridge-owned tab and using the bridge `pageAssets`
153
+ capability to inventory and bundle the latest non-SVG image asset. This is an
154
+ implementation detail of the TypeScript runtime; the wire command and result
155
+ shape are unchanged.
156
+
157
+ `files.downloadLatest` preserves the existing file-link behavior and falls back
158
+ to generated-artifact download only when no conventional ChatGPT file affordance
159
+ is visible. Artifact failures are reported as structured blockers such as
160
+ `artifact_unavailable`, `artifact_selector_drift`, or
161
+ `artifact_download_unavailable`, not protocol errors.
113
162
 
114
163
  `session.bootstrap` accepts `existingTab` for explicit reuse of a user-open Chrome tab before any read or prompt step. The wire shape is shared by TypeScript and Python:
115
164
 
@@ -166,7 +215,7 @@ Important: in Codex, `globalThis.agent` is not present until the Chrome plugin r
166
215
  The live Chrome bootstrap is:
167
216
 
168
217
  ```js
169
- const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/26.602.40724/scripts/browser-client.mjs");
218
+ const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/latest/scripts/browser-client.mjs");
170
219
  await setupBrowserRuntime({ globals: globalThis });
171
220
  globalThis.browser = await agent.browsers.get("extension");
172
221
  ```
@@ -219,6 +268,49 @@ Python SDK -> stdio relay -> bridge-hosted Node backend -> Codex Chrome bridge -
219
268
 
220
269
  The smoke covers `runner.run`, `runner.run_streamed`, `responses.create`, named `run_plan`, and redacted `reports.create`. It writes redacted JSON summaries and does not persist raw prompt/response content by default.
221
270
 
271
+ ## Untrusted Output And Integrity
272
+
273
+ Assistant output captured from ChatGPT is untrusted third-party content. Runner results with non-empty `output_text` expose `data.untrustedOutput`; Responses-shaped results expose the same object at `browser_control.untrustedOutput`.
274
+
275
+ The envelope schema is:
276
+
277
+ ```json
278
+ {
279
+ "schemaVersion": "chatgpt.browser_control.untrusted_output_return.v1",
280
+ "trusted": false,
281
+ "source": "chatgpt",
282
+ "capturedAt": "2026-06-06T00:00:00.000Z",
283
+ "contentSha256": "8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4",
284
+ "contentBytes": 2,
285
+ "inline": true,
286
+ "maxInlineBytes": 12000,
287
+ "rendered": "UNTRUSTED OUTPUT RETURN ENVELOPE\n..."
288
+ }
289
+ ```
290
+
291
+ Use `rendered` when handing the captured answer to another agent, tool, or prompt. It places routing and hash metadata before the body, tells the consumer not to execute embedded instructions, and uses a markdown fence longer than any backtick run inside the content. Outputs larger than the inline byte guard are not inlined; the envelope points at the persisted output path when one is available.
292
+
293
+ Run report creation writes a sibling `*.meta.json` sidecar by default:
294
+
295
+ ```json
296
+ {
297
+ "schemaVersion": "chatgpt.browser_control.integrity.v1",
298
+ "target": {
299
+ "path": "reports/runs/run.json",
300
+ "bytes": 123,
301
+ "sha256": "..."
302
+ },
303
+ "output": {
304
+ "untrusted": true,
305
+ "bytes": 2,
306
+ "sha256": "..."
307
+ },
308
+ "inputs": []
309
+ }
310
+ ```
311
+
312
+ The sidecar hashes the report file, normalized prompt text when available, untrusted output text when available, and configured input file paths. Consumers that cross a process or trust boundary should rehash the sidecar targets before acting on the report. Report writes are atomic and refuse to overwrite an existing target path.
313
+
222
314
  ## Contract Fixtures
223
315
 
224
316
  Shared fixtures live under:
@@ -231,7 +323,9 @@ Required gates:
231
323
 
232
324
  ```bash
233
325
  npm run contract:validate
326
+ npm run docs:drift
234
327
  npm run parity:fixtures
328
+ npm run parity:suite
235
329
  npm run test:backend-conformance
236
330
  ```
237
331
 
@@ -0,0 +1,142 @@
1
+ # Language Coverage & Capture Tracker
2
+
3
+ The full set of languages ChatGPT exposes in **Settings → General → Language** (captured
4
+ 2026-06-09), with a rough total-speaker estimate and capture status. Use this to track the
5
+ localization rollout described in [`localization.md`](./localization.md). Strings are
6
+ stored per-locale under `src/dom/locale/<bcp47>.ts`.
7
+
8
+ - Speaker counts are **crude guesstimates** (native + second-language, rounded), not
9
+ researched figures. They exist only to prioritize the rollout.
10
+ - `bcp47` is the suggested per-locale file name. Regional variants ChatGPT lists separately
11
+ (Spanish, French, Portuguese, Chinese) are separate registry entries — capture the
12
+ larger-reach variant first; the others can be filled later or alias the base.
13
+ - Status: ✅ done · 🟡 in progress · ⬜ pending.
14
+
15
+ ## Capture queue — top 15 by crude speaker count
16
+
17
+ English is the canonical baseline (already in the registry), so the queue below is the top
18
+ 15 **non-English** languages by rough world speaker count. Order is a best-effort
19
+ guesstimate; the tail (≈#11–15) is close and easily reordered.
20
+
21
+ | # | Language | Native | bcp47 | ~Speakers | Status |
22
+ |---|---|---|---|---|---|
23
+ | — | English (US) | English (US) | `en-US` | ~1.5B | ✅ baseline |
24
+ | 1 | Chinese (Simplified) | 简体中文 | `zh-Hans` | ~1.1B | ✅ |
25
+ | 2 | Hindi | हिन्दी | `hi` | ~600M | ✅ |
26
+ | 3 | Spanish (Latin America) | español (Latinoamérica) | `es-419` | ~450M | ✅ |
27
+ | 4 | Arabic | العربية | `ar` | ~400M | ✅ |
28
+ | 5 | French (France) | français (France) | `fr-FR` | ~300M | ✅ |
29
+ | 6 | Bengali | বাংলা | `bn` | ~270M | ✅ |
30
+ | 7 | Portuguese (Brazil) | português (Brasil) | `pt-BR` | ~260M | ✅ |
31
+ | 8 | Russian | русский | `ru` | ~255M | ✅ |
32
+ | 9 | Urdu | اردو | `ur` | ~230M | ✅ |
33
+ | 10 | Indonesian | Indonesia | `id` | ~200M | ✅ |
34
+ | 11 | German | Deutsch | `de` | ~135M | ✅ |
35
+ | 12 | Punjabi | ਪੰਜਾਬੀ | `pa` | ~130M | ✅ |
36
+ | 13 | Japanese | 日本語 | `ja` | ~125M | ✅ |
37
+ | 14 | Marathi | मराठी | `mr` | ~95M | ✅ |
38
+ | 15 | Turkish | Türkçe | `tr` | ~85M | ✅ |
39
+
40
+ > Note: this ranks by **raw speaker count** as requested. If you'd rather weight by likely
41
+ > ChatGPT user base, the order would tilt toward European/East-Asian locales (Japanese,
42
+ > German, French, Spanish, Portuguese, Korean, Italian) and away from Punjabi/Marathi/Bengali.
43
+
44
+ ## Full language list
45
+
46
+ | Language | Native | bcp47 | ~Speakers | Status |
47
+ |---|---|---|---|---|
48
+ | English (US) | English (US) | `en-US` | ~1.5B | ✅ |
49
+ | Amharic | አማርኛ | `am` | ~60M | ✅ |
50
+ | Arabic | العربية | `ar` | ~400M | ✅ |
51
+ | Bulgarian | български | `bg` | ~8M | ✅ |
52
+ | Bengali | বাংলা | `bn` | ~270M | ✅ |
53
+ | Bosnian | bosanski | `bs` | ~3M | ✅ |
54
+ | Catalan | català | `ca` | ~10M | ✅ |
55
+ | Czech | čeština | `cs` | ~11M | ✅ |
56
+ | Danish | dansk | `da` | ~6M | ✅ |
57
+ | German | Deutsch | `de` | ~135M | ✅ |
58
+ | Greek | Ελληνικά | `el` | ~13M | ✅ |
59
+ | Spanish (Latin America) | español (Latinoamérica) | `es-419` | ~450M | ✅ |
60
+ | Spanish (Spain) | español (España) | `es-ES` | ~47M | ✅ |
61
+ | Estonian | eesti | `et` | ~1M | ✅ |
62
+ | Persian | فارسی | `fa` | ~80M | ✅ |
63
+ | Finnish | suomi | `fi` | ~5.5M | ✅ |
64
+ | French (Canada) | français (Canada) | `fr-CA` | ~10M | ✅ |
65
+ | French (France) | français (France) | `fr-FR` | ~300M | ✅ |
66
+ | Gujarati | ગુજરાતી | `gu` | ~60M | ✅ |
67
+ | Hindi | हिन्दी | `hi` | ~600M | ✅ |
68
+ | Croatian | hrvatski | `hr` | ~5M | ✅ |
69
+ | Hungarian | magyar | `hu` | ~13M | ✅ |
70
+ | Armenian | հայերեն | `hy` | ~7M | ✅ |
71
+ | Indonesian | Indonesia | `id` | ~200M | ✅ |
72
+ | Icelandic | íslenska | `is` | ~0.4M | ✅ |
73
+ | Italian | italiano | `it` | ~65M | ✅ |
74
+ | Japanese | 日本語 | `ja` | ~125M | ✅ |
75
+ | Georgian | ქართული | `ka` | ~4M | ✅ |
76
+ | Kazakh | қазақ тілі | `kk` | ~13M | ✅ |
77
+ | Kannada | ಕನ್ನಡ | `kn` | ~60M | ✅ |
78
+ | Korean | 한국어 | `ko` | ~80M | ✅ |
79
+ | Lithuanian | lietuvių | `lt` | ~3M | ✅ |
80
+ | Latvian | latviešu | `lv` | ~2M | ✅ |
81
+ | Macedonian | македонски | `mk` | ~2M | ✅ |
82
+ | Malayalam | മലയാളം | `ml` | ~38M | ✅ |
83
+ | Mongolian | монгол | `mn` | ~5M | ✅ |
84
+ | Marathi | मराठी | `mr` | ~95M | ✅ |
85
+ | Malay | Bahasa Melayu | `ms` | ~80M | ✅ |
86
+ | Burmese | မြန်မာ | `my` | ~43M | ✅ |
87
+ | Norwegian Bokmål | norsk bokmål | `nb` | ~5M | ✅ |
88
+ | Dutch | Nederlands | `nl` | ~25M | ✅ |
89
+ | Punjabi | ਪੰਜਾਬੀ | `pa` | ~130M | ✅ |
90
+ | Polish | polski | `pl` | ~45M | ✅ |
91
+ | Portuguese (Brazil) | português (Brasil) | `pt-BR` | ~210M | ✅ |
92
+ | Portuguese (Portugal) | português (Portugal) | `pt-PT` | ~10M | ✅ |
93
+ | Romanian | română | `ro` | ~24M | ✅ |
94
+ | Russian | русский | `ru` | ~255M | ✅ |
95
+ | Slovak | slovenčina | `sk` | ~5M | ✅ |
96
+ | Slovenian | slovenščina | `sl` | ~2.5M | ✅ |
97
+ | Somali | Soomaali | `so` | ~22M | ✅ |
98
+ | Albanian | shqip | `sq` | ~7.5M | ✅ |
99
+ | Serbian | српски | `sr` | ~9M | ✅ |
100
+ | Swedish | svenska | `sv` | ~10M | ✅ |
101
+ | Swahili | Kiswahili | `sw` | ~120M | ✅ |
102
+ | Tamil | தமிழ் | `ta` | ~85M | ✅ |
103
+ | Telugu | తెలుగు | `te` | ~95M | ✅ |
104
+ | Thai | ไทย | `th` | ~60M | ✅ |
105
+ | Tagalog (Filipino) | Tagalog | `tl` | ~70M | ✅ |
106
+ | Turkish | Türkçe | `tr` | ~85M | ✅ |
107
+ | Ukrainian | українська | `uk` | ~40M | ✅ |
108
+ | Urdu | اردو | `ur` | ~230M | ✅ |
109
+ | Vietnamese | Tiếng Việt | `vi` | ~85M | ✅ |
110
+ | Chinese (Simplified) | 简体中文 | `zh-Hans` | ~1.1B | ✅ |
111
+ | Chinese (Traditional, Hong Kong) | 繁體中文(香港) | `zh-HK` | ~85M | ✅ |
112
+ | Chinese (Traditional, Taiwan) | 繁體中文(台灣) | `zh-TW` | ~23M | ✅ |
113
+
114
+ ## Automated capture
115
+
116
+ Use the repository capture script to drive the visible ChatGPT UI through Settings → General
117
+ → Language, then open the composer Intelligence picker and record the localized labels:
118
+
119
+ ```bash
120
+ npm run capture:intelligence-locales -- --auto-switch --all --if-missing open
121
+ ```
122
+
123
+ The script uses the native language names from this tracker, verifies the rendered
124
+ `document.documentElement.lang`, writes JSONL evidence under
125
+ `outputs/intelligence-locale-captures/`, and restores the initially selected language by
126
+ default. It does not write cookies, storage, or private browser state directly; if ChatGPT
127
+ changes the settings or picker DOM, the run should emit a blocker record instead of guessing.
128
+
129
+ ## Capture procedure (per language)
130
+
131
+ 1. Switch ChatGPT **Settings → General → Language** to the target, reload, and confirm via
132
+ `document.documentElement.lang` + visible text.
133
+ 2. Read static controls (composer, add-files, new chat, current model label) via JS.
134
+ 3. Open the model switcher and the `+` menu with **real** clicks (Radix menus ignore
135
+ synthetic `.click()`) and read the menu items → mode + tool labels.
136
+ 4. Open search → placeholder; observe a conversation → copy-response + response-actions.
137
+ `stopControl` is only present mid-generation; login/captcha/rate-limit copy needs a
138
+ logged-out/limited state — these may lag and rely on the English fallback + the
139
+ `selector_drift` safety net until captured.
140
+ 5. Write the verified strings to `src/dom/locale/<bcp47>.ts` and register it in
141
+ `src/dom/locale/index.ts`. Build + test + bundle + sync.
142
+ 6. Restore the account language to English (US) when the run is complete.