codex-chatgpt-control 0.1.0-alpha.1 → 0.2.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 (242) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +55 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +7 -0
  4. package/contracts/v1/fixtures/blocker-explanation-profiles.json +114 -0
  5. package/contracts/v1/fixtures/command-descriptors.json +192 -15
  6. package/contracts/v1/fixtures/doctor-bridge-upload.json +8 -0
  7. package/contracts/v1/fixtures/doctor-scenario-preflight.json +108 -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 +72 -6
  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 +5137 -488
  30. package/dist/codex-chatgpt-control.bundle.mjs +5001 -286
  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 +7 -0
  35. package/dist/src/backend/session.js +14 -0
  36. package/dist/src/browser/attach.js +128 -20
  37. package/dist/src/browser/downloads.js +2 -1
  38. package/dist/src/browser/page-state.js +6 -3
  39. package/dist/src/client.d.ts +16 -1
  40. package/dist/src/client.js +86 -7
  41. package/dist/src/commands/artifacts.d.ts +5 -0
  42. package/dist/src/commands/artifacts.js +432 -0
  43. package/dist/src/commands/context.js +5 -2
  44. package/dist/src/commands/doctor.d.ts +10 -2
  45. package/dist/src/commands/doctor.js +242 -14
  46. package/dist/src/commands/files.d.ts +2 -1
  47. package/dist/src/commands/files.js +352 -18
  48. package/dist/src/commands/messages.d.ts +2 -1
  49. package/dist/src/commands/messages.js +232 -40
  50. package/dist/src/commands/modes.js +202 -20
  51. package/dist/src/commands/project-sources.d.ts +10 -0
  52. package/dist/src/commands/project-sources.js +643 -0
  53. package/dist/src/commands/registry.js +76 -14
  54. package/dist/src/commands/reports.d.ts +9 -0
  55. package/dist/src/commands/reports.js +83 -6
  56. package/dist/src/commands/response-actions.js +33 -0
  57. package/dist/src/commands/sequence.js +14 -0
  58. package/dist/src/commands/threads.js +2 -1
  59. package/dist/src/commands/timeouts.d.ts +2 -0
  60. package/dist/src/commands/timeouts.js +18 -0
  61. package/dist/src/diagnostics/blockers.d.ts +54 -0
  62. package/dist/src/diagnostics/blockers.js +299 -0
  63. package/dist/src/dom/artifacts.d.ts +7 -0
  64. package/dist/src/dom/artifacts.js +198 -0
  65. package/dist/src/dom/generation-state.d.ts +8 -0
  66. package/dist/src/dom/generation-state.js +103 -0
  67. package/dist/src/dom/locale/am.d.ts +28 -0
  68. package/dist/src/dom/locale/am.js +28 -0
  69. package/dist/src/dom/locale/ar.d.ts +28 -0
  70. package/dist/src/dom/locale/ar.js +28 -0
  71. package/dist/src/dom/locale/bg.d.ts +28 -0
  72. package/dist/src/dom/locale/bg.js +28 -0
  73. package/dist/src/dom/locale/bn.d.ts +28 -0
  74. package/dist/src/dom/locale/bn.js +28 -0
  75. package/dist/src/dom/locale/bs.d.ts +28 -0
  76. package/dist/src/dom/locale/bs.js +28 -0
  77. package/dist/src/dom/locale/ca.d.ts +28 -0
  78. package/dist/src/dom/locale/ca.js +28 -0
  79. package/dist/src/dom/locale/cs.d.ts +28 -0
  80. package/dist/src/dom/locale/cs.js +28 -0
  81. package/dist/src/dom/locale/da.d.ts +28 -0
  82. package/dist/src/dom/locale/da.js +28 -0
  83. package/dist/src/dom/locale/de.d.ts +30 -0
  84. package/dist/src/dom/locale/de.js +30 -0
  85. package/dist/src/dom/locale/el.d.ts +28 -0
  86. package/dist/src/dom/locale/el.js +28 -0
  87. package/dist/src/dom/locale/en.d.ts +50 -0
  88. package/dist/src/dom/locale/en.js +56 -0
  89. package/dist/src/dom/locale/es-419.d.ts +28 -0
  90. package/dist/src/dom/locale/es-419.js +28 -0
  91. package/dist/src/dom/locale/es-ES.d.ts +28 -0
  92. package/dist/src/dom/locale/es-ES.js +28 -0
  93. package/dist/src/dom/locale/et.d.ts +28 -0
  94. package/dist/src/dom/locale/et.js +28 -0
  95. package/dist/src/dom/locale/fa.d.ts +28 -0
  96. package/dist/src/dom/locale/fa.js +28 -0
  97. package/dist/src/dom/locale/fi.d.ts +28 -0
  98. package/dist/src/dom/locale/fi.js +28 -0
  99. package/dist/src/dom/locale/fr-CA.d.ts +29 -0
  100. package/dist/src/dom/locale/fr-CA.js +29 -0
  101. package/dist/src/dom/locale/fr-FR.d.ts +30 -0
  102. package/dist/src/dom/locale/fr-FR.js +30 -0
  103. package/dist/src/dom/locale/gu.d.ts +28 -0
  104. package/dist/src/dom/locale/gu.js +28 -0
  105. package/dist/src/dom/locale/hi.d.ts +28 -0
  106. package/dist/src/dom/locale/hi.js +28 -0
  107. package/dist/src/dom/locale/hr.d.ts +28 -0
  108. package/dist/src/dom/locale/hr.js +28 -0
  109. package/dist/src/dom/locale/hu.d.ts +28 -0
  110. package/dist/src/dom/locale/hu.js +28 -0
  111. package/dist/src/dom/locale/hy.d.ts +28 -0
  112. package/dist/src/dom/locale/hy.js +28 -0
  113. package/dist/src/dom/locale/id.d.ts +28 -0
  114. package/dist/src/dom/locale/id.js +28 -0
  115. package/dist/src/dom/locale/index.d.ts +79 -0
  116. package/dist/src/dom/locale/index.js +202 -0
  117. package/dist/src/dom/locale/is.d.ts +28 -0
  118. package/dist/src/dom/locale/is.js +28 -0
  119. package/dist/src/dom/locale/it.d.ts +27 -0
  120. package/dist/src/dom/locale/it.js +27 -0
  121. package/dist/src/dom/locale/ja.d.ts +27 -0
  122. package/dist/src/dom/locale/ja.js +27 -0
  123. package/dist/src/dom/locale/ka.d.ts +28 -0
  124. package/dist/src/dom/locale/ka.js +28 -0
  125. package/dist/src/dom/locale/kk.d.ts +28 -0
  126. package/dist/src/dom/locale/kk.js +28 -0
  127. package/dist/src/dom/locale/kn.d.ts +28 -0
  128. package/dist/src/dom/locale/kn.js +28 -0
  129. package/dist/src/dom/locale/ko.d.ts +28 -0
  130. package/dist/src/dom/locale/ko.js +28 -0
  131. package/dist/src/dom/locale/lt.d.ts +28 -0
  132. package/dist/src/dom/locale/lt.js +28 -0
  133. package/dist/src/dom/locale/lv.d.ts +28 -0
  134. package/dist/src/dom/locale/lv.js +28 -0
  135. package/dist/src/dom/locale/mk.d.ts +28 -0
  136. package/dist/src/dom/locale/mk.js +28 -0
  137. package/dist/src/dom/locale/ml.d.ts +28 -0
  138. package/dist/src/dom/locale/ml.js +28 -0
  139. package/dist/src/dom/locale/mn.d.ts +28 -0
  140. package/dist/src/dom/locale/mn.js +28 -0
  141. package/dist/src/dom/locale/mr.d.ts +28 -0
  142. package/dist/src/dom/locale/mr.js +28 -0
  143. package/dist/src/dom/locale/ms.d.ts +28 -0
  144. package/dist/src/dom/locale/ms.js +28 -0
  145. package/dist/src/dom/locale/my.d.ts +28 -0
  146. package/dist/src/dom/locale/my.js +28 -0
  147. package/dist/src/dom/locale/nb.d.ts +28 -0
  148. package/dist/src/dom/locale/nb.js +28 -0
  149. package/dist/src/dom/locale/nl.d.ts +28 -0
  150. package/dist/src/dom/locale/nl.js +28 -0
  151. package/dist/src/dom/locale/pa.d.ts +28 -0
  152. package/dist/src/dom/locale/pa.js +28 -0
  153. package/dist/src/dom/locale/pl.d.ts +28 -0
  154. package/dist/src/dom/locale/pl.js +28 -0
  155. package/dist/src/dom/locale/pt-BR.d.ts +28 -0
  156. package/dist/src/dom/locale/pt-BR.js +28 -0
  157. package/dist/src/dom/locale/pt-PT.d.ts +28 -0
  158. package/dist/src/dom/locale/pt-PT.js +28 -0
  159. package/dist/src/dom/locale/ro.d.ts +28 -0
  160. package/dist/src/dom/locale/ro.js +28 -0
  161. package/dist/src/dom/locale/ru.d.ts +28 -0
  162. package/dist/src/dom/locale/ru.js +28 -0
  163. package/dist/src/dom/locale/sk.d.ts +28 -0
  164. package/dist/src/dom/locale/sk.js +28 -0
  165. package/dist/src/dom/locale/sl.d.ts +28 -0
  166. package/dist/src/dom/locale/sl.js +28 -0
  167. package/dist/src/dom/locale/so.d.ts +28 -0
  168. package/dist/src/dom/locale/so.js +28 -0
  169. package/dist/src/dom/locale/sq.d.ts +28 -0
  170. package/dist/src/dom/locale/sq.js +28 -0
  171. package/dist/src/dom/locale/sr.d.ts +28 -0
  172. package/dist/src/dom/locale/sr.js +28 -0
  173. package/dist/src/dom/locale/sv.d.ts +28 -0
  174. package/dist/src/dom/locale/sv.js +28 -0
  175. package/dist/src/dom/locale/sw.d.ts +28 -0
  176. package/dist/src/dom/locale/sw.js +28 -0
  177. package/dist/src/dom/locale/ta.d.ts +28 -0
  178. package/dist/src/dom/locale/ta.js +28 -0
  179. package/dist/src/dom/locale/te.d.ts +28 -0
  180. package/dist/src/dom/locale/te.js +28 -0
  181. package/dist/src/dom/locale/th.d.ts +28 -0
  182. package/dist/src/dom/locale/th.js +28 -0
  183. package/dist/src/dom/locale/tl.d.ts +28 -0
  184. package/dist/src/dom/locale/tl.js +28 -0
  185. package/dist/src/dom/locale/tr.d.ts +28 -0
  186. package/dist/src/dom/locale/tr.js +28 -0
  187. package/dist/src/dom/locale/types.d.ts +45 -0
  188. package/dist/src/dom/locale/types.js +1 -0
  189. package/dist/src/dom/locale/uk.d.ts +28 -0
  190. package/dist/src/dom/locale/uk.js +28 -0
  191. package/dist/src/dom/locale/ur.d.ts +28 -0
  192. package/dist/src/dom/locale/ur.js +28 -0
  193. package/dist/src/dom/locale/vi.d.ts +28 -0
  194. package/dist/src/dom/locale/vi.js +28 -0
  195. package/dist/src/dom/locale/zh-HK.d.ts +28 -0
  196. package/dist/src/dom/locale/zh-HK.js +28 -0
  197. package/dist/src/dom/locale/zh-Hans.d.ts +28 -0
  198. package/dist/src/dom/locale/zh-Hans.js +28 -0
  199. package/dist/src/dom/locale/zh-TW.d.ts +29 -0
  200. package/dist/src/dom/locale/zh-TW.js +29 -0
  201. package/dist/src/dom/locale-labels.d.ts +26 -0
  202. package/dist/src/dom/locale-labels.js +26 -0
  203. package/dist/src/dom/menus.d.ts +6 -0
  204. package/dist/src/dom/menus.js +34 -8
  205. package/dist/src/dom/message-format.d.ts +2 -1
  206. package/dist/src/dom/message-format.js +67 -10
  207. package/dist/src/dom/messages.d.ts +2 -1
  208. package/dist/src/dom/messages.js +2 -4
  209. package/dist/src/dom/selectors.d.ts +1 -0
  210. package/dist/src/dom/selectors.js +42 -15
  211. package/dist/src/index.d.ts +5 -0
  212. package/dist/src/index.js +5 -0
  213. package/dist/src/platform/local-paths.d.ts +4 -0
  214. package/dist/src/platform/local-paths.js +20 -0
  215. package/dist/src/runner/interruptions.js +15 -4
  216. package/dist/src/runner/responses.js +18 -1
  217. package/dist/src/runner/result.js +15 -0
  218. package/dist/src/runner/resume.js +1 -0
  219. package/dist/src/runner/types.d.ts +3 -0
  220. package/dist/src/safety/risk.d.ts +7 -0
  221. package/dist/src/safety/risk.js +7 -0
  222. package/dist/src/safety/untrusted-output.d.ts +73 -0
  223. package/dist/src/safety/untrusted-output.js +199 -0
  224. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +1 -0
  225. package/dist/src/scripts/apply-intelligence-locale-captures.js +184 -0
  226. package/dist/src/scripts/capture-intelligence-locales.d.ts +25 -0
  227. package/dist/src/scripts/capture-intelligence-locales.js +669 -0
  228. package/dist/src/scripts/check-surface-drift.d.ts +1 -0
  229. package/dist/src/scripts/check-surface-drift.js +11 -0
  230. package/dist/src/scripts/live-smoke/scenarios.js +84 -0
  231. package/dist/src/scripts/locale-capture/language-coverage.d.ts +10 -0
  232. package/dist/src/scripts/locale-capture/language-coverage.js +39 -0
  233. package/dist/src/testing/surface-drift.d.ts +66 -0
  234. package/dist/src/testing/surface-drift.js +363 -0
  235. package/dist/src/types.d.ts +201 -2
  236. package/package.json +2 -1
  237. package/references/backend-protocol.md +93 -1
  238. package/references/language-coverage.md +142 -0
  239. package/references/localization.md +247 -0
  240. package/references/python-parity.md +63 -1
  241. package/references/safety.md +2 -0
  242. package/references/troubleshooting.md +58 -0
@@ -8,10 +8,10 @@ const descriptors = [
8
8
  `await chatgpt.askInThread({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, prompt: "Continue." });`
9
9
  ]),
10
10
  workflow("askWithFiles", "Attach absolute local file paths, optionally set mode, ask, wait, and read.", [
11
- `await chatgpt.askWithFiles({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, mode: { effort: "Thinking" }, files: ["/absolute/path/brief.md"], prompt: "Summarize this.", wait: true, read: { format: "markdown" } });`
11
+ `await chatgpt.askWithFiles({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, mode: { model: "Pro" }, files: ["/absolute/host/path/brief.md"], prompt: "Summarize this.", wait: true, read: { format: "markdown" } });`
12
12
  ]),
13
13
  workflow("askAndDownload", "Ask ChatGPT to produce a visible downloadable output and save the latest exposed file.", [
14
- `await chatgpt.askAndDownload({ prompt: "Create a CSV.", download: { destDir: "/tmp/out" }, wait: true });`
14
+ `await chatgpt.askAndDownload({ prompt: "Create a CSV.", download: { destDir: "/absolute/host/output" }, wait: true });`
15
15
  ]),
16
16
  workflow("runMessages", "Run sequential prompts where later prompts can use earlier step data.", [
17
17
  `await chatgpt.runMessages({ messages: [{ id: "first", prompt: "alpha" }, { id: "second", prompt: "beta" }] });`
@@ -39,10 +39,10 @@ const descriptors = [
39
39
  `await chatgpt.runPlan({ name: "find-open-copy-latest", input: { query: "SDK Design Proposal" } });`
40
40
  ]),
41
41
  workflow("attach-ask-read", "Named macro: open a new thread, attach files, ask, wait, and read Markdown.", [
42
- `await chatgpt.runPlan({ name: "attach-ask-read", input: { files: ["/absolute/path.md"], prompt: "Summarize." } });`
42
+ `await chatgpt.runPlan({ name: "attach-ask-read", input: { files: ["/absolute/host/path.md"], prompt: "Summarize." } });`
43
43
  ]),
44
44
  workflow("ask-and-download", "Named macro: ask in a new thread and download the latest file affordance.", [
45
- `await chatgpt.runPlan({ name: "ask-and-download", input: { prompt: "Create a CSV.", destDir: "/tmp/out" } });`
45
+ `await chatgpt.runPlan({ name: "ask-and-download", input: { prompt: "Create a CSV.", destDir: "/absolute/host/output" } });`
46
46
  ]),
47
47
  workflow("two-turn", "Named macro: run two sequential prompts in a new thread.", [
48
48
  `await chatgpt.runPlan({ name: "two-turn", input: { first: "alpha", second: "beta" } });`
@@ -53,11 +53,14 @@ const descriptors = [
53
53
  report("redacted-run-report", "Named macro: create a redacted report for a supplied CommandResult.", [
54
54
  `await chatgpt.runPlan({ name: "redacted-run-report", input: { result } });`
55
55
  ]),
56
- diagnostic("doctor", "Preflight browser bridge, login, upload, download, clipboard, mode, and tool readiness.", [
57
- `await chatgpt.doctor({ check: ["bridge", "login", "upload"] });`
56
+ diagnostic("doctor", "Preflight browser bridge, login, upload, local files, existing-tab, artifact, localization, report, and selector readiness.", [
57
+ `await chatgpt.doctor({ check: ["bridge", "login", "upload"] });`,
58
+ `await chatgpt.doctor({ check: ["existing_tab"], existingTab: { target: { type: "conversationId", conversationId: "<conversation-id>" }, ifMissing: "block" } });`,
59
+ `await chatgpt.doctor({ check: ["file_preflight"], files: ["/absolute/host/path.md"] });`,
60
+ `await chatgpt.doctor({ check: ["localization", "reports"], report: { destDir: "/absolute/host/reports" } });`
58
61
  ]),
59
62
  report("createReport", "Write a durable redacted run report for a command result.", [
60
- `await chatgpt.createReport(result, { destDir: "/tmp/reports" });`
63
+ `await chatgpt.createReport(result, { destDir: "/absolute/host/reports" });`
61
64
  ]),
62
65
  primitive("session.bootstrap", "Attach to ChatGPT in Chrome and detect login/blocker state.", 30000),
63
66
  primitive("threads.new", "Open a new ChatGPT thread.", 30000),
@@ -69,10 +72,17 @@ const descriptors = [
69
72
  primitive("messages.wait", "Wait for the latest assistant response to stabilize.", 120000),
70
73
  primitive("messages.readLatest", "Read the latest message as Markdown, normalized text, blocks, or HTML.", 30000),
71
74
  primitive("messages.waitAndRead", "Wait for completion and read the latest message.", 120000),
75
+ primitive("artifacts.listLatest", "Detect the latest visible generated ChatGPT artifact, such as an image-only result.", 30000),
76
+ primitive("artifacts.wait", "Wait for a visible generated ChatGPT artifact to appear and stabilize.", 120000),
77
+ primitive("artifacts.downloadLatest", "Download or save the latest visible generated ChatGPT artifact.", 120000),
78
+ primitive("files.preflight", "Validate local file paths, size limits, duplicates, zero-byte files, and extension-based MIME/category guesses without opening ChatGPT.", 30000),
72
79
  primitive("files.attach", "Attach absolute local file paths through visible ChatGPT upload controls.", 180000),
73
80
  primitive("files.downloadLatest", "Download the latest visible ChatGPT file affordance.", 120000),
81
+ primitive("projects.sources.list", "Open or claim a visible ChatGPT Project Sources tab and list source names/statuses without source contents.", 30000),
82
+ primitive("projects.sources.planAdd", "Dry-run an append-only Project Sources file add from explicit local files without opening ChatGPT.", 30000),
83
+ primitive("projects.sources.add", "Append explicit local files to a visible ChatGPT Project Sources list after confirmMutation: true.", 180000),
74
84
  primitive("response.copy", "Click Copy response and return clipboard Markdown, with DOM fallback.", 5000),
75
- primitive("modes.set", "Select a visible model or effort candidate when unambiguous.", 30000),
85
+ primitive("modes.set", "Select a visible model, intelligence, effort, or nested model-version candidate when unambiguous.", 30000),
76
86
  primitive("tools.select", "Select a visible ChatGPT tool when unambiguous.", 30000)
77
87
  ];
78
88
  export function commandDescriptors() {
@@ -186,7 +196,7 @@ function workflowArgs(name) {
186
196
  prompt: "message to send after files are attached",
187
197
  thread: "optional thread selector",
188
198
  existingTab: "true or explicit policy to claim a user-open Chrome tab instead of opening a replacement",
189
- mode: "optional visible mode selection, e.g. { effort: \"Thinking\" }",
199
+ mode: "optional visible mode selection, e.g. { model: \"Pro\" } or { intelligence: \"Pro\", modelVersion: \"5.4\" }",
190
200
  wait: "true or wait options; defaults to true",
191
201
  read: "true or read options such as { format: \"markdown\" }; defaults to Markdown",
192
202
  report: "optional redacted report settings"
@@ -209,7 +219,12 @@ function workflowDefaults(name) {
209
219
  function diagnosticArgs(name) {
210
220
  if (name === "doctor-upload")
211
221
  return {};
212
- return { check: "optional list of readiness checks" };
222
+ return {
223
+ check: "optional list of readiness checks",
224
+ existingTab: "optional exact existing-tab policy for check: [\"existing_tab\"]",
225
+ files: "optional file paths for check: [\"file_preflight\"]",
226
+ report: "optional report output policy for check: [\"reports\"]"
227
+ };
213
228
  }
214
229
  function reportArgs(name) {
215
230
  if (name === "redacted-run-report")
@@ -219,16 +234,33 @@ function reportArgs(name) {
219
234
  function primitiveArgs(name) {
220
235
  if (name === "messages.readLatest")
221
236
  return { role: "assistant or user", format: "markdown, normalized_text, visible_text, html, blocks, or all" };
237
+ if (name === "artifacts.listLatest")
238
+ return { kind: "artifact kind; currently image", max: "maximum artifacts to return" };
239
+ if (name === "artifacts.wait")
240
+ return { kind: "artifact kind; currently image", afterArtifactCount: "baseline artifact count", requireDownload: "wait until a download affordance is visible" };
241
+ if (name === "artifacts.downloadLatest")
242
+ return { destDir: "download destination directory", prefer: "download_control or visible_image_source" };
222
243
  if (name === "response.copy")
223
244
  return { prefer: "clipboard or dom", format: "markdown, normalized_text, visible_text, html, blocks, or all" };
224
245
  if (name.startsWith("threads.search"))
225
246
  return { query: "history search query" };
247
+ if (name === "files.preflight")
248
+ return { paths: "absolute local file paths", maxBytesPerFile: "optional local per-file byte limit", maxTotalBytes: "optional local total byte limit" };
226
249
  if (name.startsWith("files.attach"))
227
250
  return { paths: "absolute local file paths" };
251
+ if (name === "projects.sources.list")
252
+ return { projectUrl: "ChatGPT Project URL such as https://chatgpt.com/g/g-p-.../project", existingTab: "optional exact existing-tab policy", timeoutMs: "optional browser timeout" };
253
+ if (name === "projects.sources.planAdd")
254
+ return { projectUrl: "ChatGPT Project URL", files: "explicit absolute local file paths", batchSize: "optional upload batch size" };
255
+ if (name === "projects.sources.add")
256
+ return { projectUrl: "ChatGPT Project URL", files: "explicit absolute local file paths", confirmMutation: "must be true to mutate Project Sources", batchSize: "optional upload batch size" };
228
257
  if (name === "modes.set") {
229
258
  return {
230
- effort: "visible effort label such as Thinking or Extended",
231
- model: "visible model label such as Instant, Pro, or another available model",
259
+ effort: "visible legacy effort label such as Thinking or Extended",
260
+ intelligence: "visible intelligence label such as Medium, High, Extra High, or Pro",
261
+ model: "visible model label such as Pro, GPT-5.5, or another available model",
262
+ modelVersion: "visible nested model version such as 5.5, 5.4, 4.5, or o3",
263
+ version: "alias for modelVersion",
232
264
  timeoutMs: "optional timeout for opening and selecting the visible mode menu"
233
265
  };
234
266
  }
@@ -237,20 +269,50 @@ function primitiveArgs(name) {
237
269
  function primitiveExamples(name) {
238
270
  if (name === "modes.set") {
239
271
  return [
272
+ `await chatgpt.modes.set({ model: "Pro" });`,
273
+ `await chatgpt.modes.set({ intelligence: "Pro", modelVersion: "5.4" });`,
240
274
  `await chatgpt.modes.set({ effort: "Thinking" });`,
241
- `await chatgpt.askWithFiles({ mode: { effort: "Thinking" }, files: ["/absolute/path.jpg"], prompt: "Describe this image.", wait: true });`
275
+ `await chatgpt.askWithFiles({ mode: { model: "Pro" }, files: ["/absolute/host/path.jpg"], prompt: "Describe this image.", wait: true });`
276
+ ];
277
+ }
278
+ if (name === "files.preflight") {
279
+ return [
280
+ `await chatgpt.files.preflight({ paths: ["/absolute/host/path.md"] });`
242
281
  ];
243
282
  }
244
283
  if (name === "files.attach") {
245
- return [`await chatgpt.files.attach({ paths: ["/absolute/path.jpg"] });`];
284
+ return [
285
+ `await chatgpt.files.attach({ paths: ["/absolute/host/path.jpg"] });`,
286
+ String.raw `// On Windows backend hosts, use paths such as C:\Users\you\Pictures\image.jpg.`
287
+ ];
288
+ }
289
+ if (name === "projects.sources.list") {
290
+ return [`await chatgpt.projects.sources.list({ projectUrl: "https://chatgpt.com/g/g-p-example/project" });`];
291
+ }
292
+ if (name === "projects.sources.planAdd") {
293
+ return [`await chatgpt.projects.sources.planAdd({ projectUrl: "https://chatgpt.com/g/g-p-example/project", files: ["/absolute/host/path.md"] });`];
294
+ }
295
+ if (name === "projects.sources.add") {
296
+ return [`await chatgpt.projects.sources.add({ projectUrl: "https://chatgpt.com/g/g-p-example/project", files: ["/absolute/host/path.md"], confirmMutation: true });`];
297
+ }
298
+ if (name.startsWith("artifacts.")) {
299
+ return [`await chatgpt.artifacts.downloadLatest({ destDir: "/absolute/host/output" });`];
246
300
  }
247
301
  return [];
248
302
  }
249
303
  function primitiveBlockers(name) {
304
+ if (name === "files.preflight")
305
+ return ["not_found", "permission", "upload_failed"];
250
306
  if (name.startsWith("files.attach"))
251
307
  return ["browser_bridge_unavailable", "login_required", "permission", "upload_failed"];
252
308
  if (name.startsWith("files.download"))
253
309
  return ["browser_bridge_unavailable", "login_required", "download_unavailable"];
310
+ if (name === "projects.sources.planAdd")
311
+ return ["not_found", "permission", "upload_failed"];
312
+ if (name.startsWith("projects.sources."))
313
+ return ["browser_bridge_unavailable", "login_required", "selector_drift", "confirmation", "permission", "upload_failed"];
314
+ if (name.startsWith("artifacts."))
315
+ return ["browser_bridge_unavailable", "login_required", "artifact_unavailable", "artifact_selector_drift", "artifact_download_unavailable"];
254
316
  if (name.startsWith("modes.") || name.startsWith("tools."))
255
317
  return ["browser_bridge_unavailable", "login_required", "selector_drift"];
256
318
  return commonBlockers();
@@ -1,14 +1,23 @@
1
+ import { type IntegritySidecar } from "../safety/untrusted-output.js";
1
2
  import type { CommandResult, RuntimeEnv } from "../types.js";
3
+ export type RunReportIntegrityOptions = {
4
+ inputPaths?: string[];
5
+ prompt?: string;
6
+ outputText?: string;
7
+ };
2
8
  export type RunReportOptions = {
3
9
  enabled?: boolean;
4
10
  destDir?: string;
5
11
  basename?: string;
6
12
  includeContent?: boolean;
7
13
  maxPreviewChars?: number;
14
+ integrity?: boolean | RunReportIntegrityOptions;
8
15
  };
9
16
  export type RunReportData = {
10
17
  path: string;
11
18
  bytes: number;
12
19
  includeContent: boolean;
20
+ metaPath?: string;
21
+ integrity?: Pick<IntegritySidecar, "schemaVersion" | "target" | "prompt" | "output" | "inputs">;
13
22
  };
14
23
  export declare function createRunReport(env: RuntimeEnv, result: CommandResult<unknown>, options?: RunReportOptions): Promise<CommandResult<RunReportData>>;
@@ -1,13 +1,14 @@
1
- import { mkdir, stat, writeFile } from "node:fs/promises";
2
1
  import { join } from "node:path";
3
2
  import { resultError, resultOk } from "../errors.js";
4
3
  import { redactReportValue } from "../safety/report-redaction.js";
4
+ import { writeFileAtomicNoOverwrite, writeJsonArtifactWithIntegrity } from "../safety/untrusted-output.js";
5
5
  import { contextFromPage } from "./context.js";
6
6
  export async function createRunReport(env, result, options = {}) {
7
7
  try {
8
8
  const destDir = options.destDir ?? "reports/runs";
9
- await mkdir(destDir, { recursive: true });
10
- const stamp = new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
9
+ const now = env.now?.() ?? new Date();
10
+ const createdAt = now.toISOString();
11
+ const stamp = createdAt.replaceAll(":", "-").replaceAll(".", "-");
11
12
  const safeBase = sanitizeBasename(options.basename ?? "chatgpt-run-report");
12
13
  const path = join(destDir, `${stamp}-${safeBase}.json`);
13
14
  const includeContent = options.includeContent === true;
@@ -31,9 +32,36 @@ export async function createRunReport(env, result, options = {}) {
31
32
  })),
32
33
  data: redactReportValue(result.data, options)
33
34
  };
34
- await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, "utf8");
35
- const saved = await stat(path);
36
- return resultOk({ path, bytes: saved.size, includeContent }, await contextFromPage(env.page));
35
+ if (options.integrity === false) {
36
+ const payload = `${JSON.stringify(report, null, 2)}\n`;
37
+ await writeFileAtomicNoOverwrite(path, payload);
38
+ return resultOk({ path, bytes: Buffer.byteLength(payload, "utf8"), includeContent }, await contextFromPage(env.page));
39
+ }
40
+ const integrity = integrityOptions(result, options.integrity);
41
+ const writeOptions = { createdAt };
42
+ if (integrity.prompt !== undefined)
43
+ writeOptions.prompt = integrity.prompt;
44
+ if (integrity.outputText !== undefined)
45
+ writeOptions.outputText = integrity.outputText;
46
+ if (integrity.inputPaths !== undefined)
47
+ writeOptions.inputPaths = integrity.inputPaths;
48
+ const saved = await writeJsonArtifactWithIntegrity(path, report, writeOptions);
49
+ const reportIntegrity = {
50
+ schemaVersion: saved.sidecar.schemaVersion,
51
+ target: saved.sidecar.target,
52
+ inputs: saved.sidecar.inputs
53
+ };
54
+ if (saved.sidecar.prompt !== undefined)
55
+ reportIntegrity.prompt = saved.sidecar.prompt;
56
+ if (saved.sidecar.output !== undefined)
57
+ reportIntegrity.output = saved.sidecar.output;
58
+ return resultOk({
59
+ path,
60
+ bytes: saved.bytes,
61
+ includeContent,
62
+ metaPath: saved.metaPath,
63
+ integrity: reportIntegrity
64
+ }, await contextFromPage(env.page));
37
65
  }
38
66
  catch (error) {
39
67
  return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(env.page));
@@ -45,3 +73,52 @@ function sanitizeBasename(name) {
45
73
  .replace(/^-+|-+$/g, "")
46
74
  || "chatgpt-run-report";
47
75
  }
76
+ function integrityOptions(result, options) {
77
+ if (typeof options === "object") {
78
+ const normalized = {
79
+ inputPaths: [...new Set(options.inputPaths ?? [])]
80
+ };
81
+ const prompt = options.prompt ?? promptFromResult(result);
82
+ const outputText = options.outputText ?? outputTextFromResult(result);
83
+ if (prompt !== undefined)
84
+ normalized.prompt = prompt;
85
+ if (outputText !== undefined)
86
+ normalized.outputText = outputText;
87
+ return normalized;
88
+ }
89
+ const normalized = {
90
+ inputPaths: []
91
+ };
92
+ const prompt = promptFromResult(result);
93
+ const outputText = outputTextFromResult(result);
94
+ if (prompt !== undefined)
95
+ normalized.prompt = prompt;
96
+ if (outputText !== undefined)
97
+ normalized.outputText = outputText;
98
+ return normalized;
99
+ }
100
+ function promptFromResult(result) {
101
+ return findStringByKey(result.data, new Set(["prompt", "input", "userTurnText"]));
102
+ }
103
+ function outputTextFromResult(result) {
104
+ if (typeof result.output_text === "string")
105
+ return result.output_text;
106
+ return findStringByKey(result.data, new Set(["responseText", "markdown", "text", "normalizedText", "visibleText"]));
107
+ }
108
+ function findStringByKey(value, keys) {
109
+ if (!isRecord(value))
110
+ return undefined;
111
+ for (const [key, child] of Object.entries(value)) {
112
+ if (keys.has(key) && typeof child === "string" && child.length > 0)
113
+ return child;
114
+ }
115
+ for (const child of Object.values(value)) {
116
+ const nested = findStringByKey(child, keys);
117
+ if (nested !== undefined)
118
+ return nested;
119
+ }
120
+ return undefined;
121
+ }
122
+ function isRecord(value) {
123
+ return value !== null && typeof value === "object" && !Array.isArray(value);
124
+ }
@@ -1,4 +1,5 @@
1
1
  import { readSystemClipboard, waitForClipboardChange } from "../browser/clipboard.js";
2
+ import { readAssistantGenerationState } from "../dom/generation-state.js";
2
3
  import { formatClipboardMarkdown, normalizeResponseFormat } from "../dom/message-format.js";
3
4
  import { resultError, resultOk } from "../errors.js";
4
5
  import { readLatestMessage, readMessages } from "../dom/messages.js";
@@ -13,6 +14,31 @@ export async function copyResponse(env, args = {}) {
13
14
  }
14
15
  const page = env.page;
15
16
  try {
17
+ if (isLatestSelection(args.which)) {
18
+ const generation = await readAssistantGenerationState(page);
19
+ if (generation.active || generation.stopped) {
20
+ const latest = await readSelectedAssistantMessage(page, args.which, args.format ?? "markdown").catch(() => undefined);
21
+ const warning = generation.active
22
+ ? "Response is still generating; copied content may be partial."
23
+ : "Response appears to have been stopped before completion; copied content may be partial.";
24
+ const fallbackReason = generation.active
25
+ ? "Response is still generating; returned DOM-derived partial content."
26
+ : "Response appears to have been stopped before completion; returned DOM-derived partial content.";
27
+ const data = latest === undefined ? undefined : copiedResponseFromExtracted(latest, "dom", fallbackReason);
28
+ const result = {
29
+ ok: false,
30
+ status: "partial",
31
+ warnings: [
32
+ warning,
33
+ ...generation.signals.map(signal => `Generation state signal: ${signal}`)
34
+ ],
35
+ context: await contextFromPage(page)
36
+ };
37
+ if (data !== undefined)
38
+ result.data = data;
39
+ return withCommandOutputText(result);
40
+ }
41
+ }
16
42
  if (args.prefer !== "dom") {
17
43
  const before = await readClipboard(env);
18
44
  const buttons = copyResponseButtons(page);
@@ -73,6 +99,9 @@ export async function copyResponse(env, args = {}) {
73
99
  return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
74
100
  }
75
101
  }
102
+ function isLatestSelection(which) {
103
+ return which === undefined || which === "latest";
104
+ }
76
105
  function readClipboard(env) {
77
106
  return env.clipboard?.read() ?? readSystemClipboard();
78
107
  }
@@ -97,6 +126,8 @@ function copiedResponseFromExtracted(latest, source, fallbackReason) {
97
126
  };
98
127
  if (latest.fidelity !== undefined)
99
128
  data.fidelity = latest.fidelity;
129
+ if (latest.captureLimit !== undefined)
130
+ data.captureLimit = latest.captureLimit;
100
131
  if (latest.warnings !== undefined || fallbackReason !== undefined) {
101
132
  data.warnings = [...(latest.warnings ?? []), ...(fallbackReason === undefined ? [] : [fallbackReason])];
102
133
  }
@@ -110,6 +141,8 @@ function mergeResponseMetadata(data, latest) {
110
141
  return;
111
142
  if (latest.markdown !== undefined && data.markdown === undefined)
112
143
  data.markdown = latest.markdown;
144
+ if (latest.captureLimit !== undefined)
145
+ data.captureLimit = latest.captureLimit;
113
146
  if (latest.visibleText !== undefined)
114
147
  data.visibleText = latest.visibleText;
115
148
  if (latest.normalizedText !== undefined)
@@ -1,4 +1,6 @@
1
+ import { downloadLatestArtifact, listLatestArtifacts, waitForArtifact } from "./artifacts.js";
1
2
  import { attachFiles, downloadLatestFile } from "./files.js";
3
+ import { addProjectSources, buildProjectSourceAddPlan, listProjectSources } from "./project-sources.js";
2
4
  import { askMessage, composeMessage, readLatest, submitMessage, waitAndRead, waitForMessage } from "./messages.js";
3
5
  import { copyResponse } from "./response-actions.js";
4
6
  import { bootstrap } from "./session.js";
@@ -59,10 +61,22 @@ export async function executeStep(step, env, previousResults) {
59
61
  return readLatest(env, step.args);
60
62
  case "messages.waitAndRead":
61
63
  return waitAndRead(env, step.args);
64
+ case "artifacts.listLatest":
65
+ return listLatestArtifacts(env, step.args);
66
+ case "artifacts.wait":
67
+ return waitForArtifact(env, step.args);
68
+ case "artifacts.downloadLatest":
69
+ return downloadLatestArtifact(env, step.args);
62
70
  case "files.attach":
63
71
  return attachFiles(env, step.args);
64
72
  case "files.downloadLatest":
65
73
  return downloadLatestFile(env, step.args);
74
+ case "projects.sources.list":
75
+ return listProjectSources(env, step.args);
76
+ case "projects.sources.planAdd":
77
+ return buildProjectSourceAddPlan(env, step.args);
78
+ case "projects.sources.add":
79
+ return addProjectSources(env, step.args);
66
80
  case "response.copy":
67
81
  return copyResponse(env, step.args);
68
82
  case "modes.set":
@@ -2,6 +2,7 @@ import { parseConversationId, readPageState } from "../browser/page-state.js";
2
2
  import { resultError, resultOk } from "../errors.js";
3
3
  import { countPageMessages, readLatestMessageText } from "../dom/messages.js";
4
4
  import { requiredLocator, searchChatsButton, searchChatsInput, newChatButton } from "../dom/selectors.js";
5
+ import { anyLabelPattern, localeLabels } from "../dom/locale-labels.js";
5
6
  import { normalizeWhitespace, stripTags } from "../dom/visible-text.js";
6
7
  import { contextFromPage } from "./context.js";
7
8
  import { bootstrap } from "./session.js";
@@ -237,7 +238,7 @@ async function openSearchUI(page) {
237
238
  async function fillSearchQuery(page, query) {
238
239
  const attempts = [
239
240
  async () => searchChatsInput(page).fill?.(query),
240
- async () => page.getByRole?.("textbox", { name: "Search chats" }).fill?.(query),
241
+ async () => page.getByRole?.("textbox", { name: anyLabelPattern(localeLabels.searchChatsButton) }).fill?.(query),
241
242
  async () => page.getByRole?.("textbox", { name: /Search chats/i }).fill?.(query),
242
243
  async () => requiredLocator(page, "input[placeholder*='Search'], [role='dialog'] input").fill?.(query)
243
244
  ];
@@ -0,0 +1,2 @@
1
+ export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T>;
2
+ export declare function localGuardTimeout(timeoutMs: number | undefined, capMs: number): number;
@@ -0,0 +1,18 @@
1
+ export async function withTimeout(promise, timeoutMs, message) {
2
+ let timeout;
3
+ try {
4
+ return await Promise.race([
5
+ promise,
6
+ new Promise((_resolve, reject) => {
7
+ timeout = setTimeout(() => reject(new Error(message)), Math.max(0, timeoutMs));
8
+ })
9
+ ]);
10
+ }
11
+ finally {
12
+ if (timeout !== undefined)
13
+ clearTimeout(timeout);
14
+ }
15
+ }
16
+ export function localGuardTimeout(timeoutMs, capMs) {
17
+ return Math.max(1, Math.min(timeoutMs ?? capMs, capMs));
18
+ }
@@ -0,0 +1,54 @@
1
+ import type { CommandContext, CommandResult, ExistingTabDiagnostics } from "../types.js";
2
+ type RemediationStep = NonNullable<NonNullable<CommandResult["blocker"]>["remediation"]>[number];
3
+ type VisibleCandidate = NonNullable<NonNullable<CommandResult["blocker"]>["candidates"]>[number];
4
+ export type BlockerSeverity = "info" | "warning" | "action_required" | "blocked";
5
+ export type BlockerCategory = "environment" | "auth" | "permission" | "ui_drift" | "user_confirmation" | "targeting" | "runtime" | "not_found" | "artifact" | "download" | "upload" | "unknown";
6
+ export type BlockerRetryGuidance = {
7
+ safe: true;
8
+ when: string;
9
+ command?: string;
10
+ } | {
11
+ safe: false;
12
+ reason: string;
13
+ };
14
+ export type BlockerResumeGuidance = {
15
+ supported: true;
16
+ stateId?: string;
17
+ command?: string;
18
+ } | {
19
+ supported: false;
20
+ reason: string;
21
+ };
22
+ export type BlockerExplanation = {
23
+ kind: string;
24
+ code?: string;
25
+ title: string;
26
+ summary: string;
27
+ severity: BlockerSeverity;
28
+ category: BlockerCategory;
29
+ userActionRequired: boolean;
30
+ retry: BlockerRetryGuidance;
31
+ resume: BlockerResumeGuidance;
32
+ remediation: RemediationStep[];
33
+ candidates?: VisibleCandidate[];
34
+ context?: {
35
+ command?: string;
36
+ url?: string;
37
+ conversationId?: string;
38
+ tabId?: string;
39
+ };
40
+ diagnostics?: {
41
+ existingTab?: ExistingTabDiagnostics;
42
+ };
43
+ nextCommands: string[];
44
+ markdown: string;
45
+ };
46
+ export type ExplainBlockerOptions = {
47
+ command?: string;
48
+ context?: Partial<CommandContext>;
49
+ stateId?: string;
50
+ nextCommands?: string[];
51
+ };
52
+ type Blocker = NonNullable<CommandResult["blocker"]>;
53
+ export declare function explainCommandBlocker(resultOrBlocker: CommandResult<unknown> | Blocker | undefined, options?: ExplainBlockerOptions): BlockerExplanation;
54
+ export {};