codex-chatgpt-control 0.2.0-alpha.1 → 0.5.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +42 -2
  3. package/contracts/v1/fixtures/backend-capabilities.json +11 -0
  4. package/contracts/v1/fixtures/command-descriptors.json +289 -7
  5. package/contracts/v1/fixtures/describe-runner-run.json +5 -2
  6. package/contracts/v1/fixtures/doctor-scenario-preflight.json +45 -2
  7. package/contracts/v1/fixtures/help-root.json +1 -1
  8. package/contracts/v1/fixtures/output-json-parse-success.json +9 -0
  9. package/contracts/v1/fixtures/reports-create-redacted.json +1 -1
  10. package/contracts/v1/fixtures/run-timeout-partial.json +15 -4
  11. package/contracts/v1/fixtures/stream-in-progress.ndjson +3 -0
  12. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +1 -1
  13. package/contracts/v1/fixtures/surface-chat-legacy.json +35 -0
  14. package/contracts/v1/fixtures/surface-chat-simplified.json +37 -0
  15. package/contracts/v1/fixtures/surface-sidebar-false-positive.json +29 -0
  16. package/contracts/v1/fixtures/surface-work-advanced.json +45 -0
  17. package/contracts/v1/fixtures/surface-work-basic.json +38 -0
  18. package/contracts/v1/fixtures/workflow-ask-success.json +8 -0
  19. package/contracts/v1/manifest.json +32 -1
  20. package/contracts/v1/parity-suite.json +121 -1
  21. package/contracts/v1/schemas/backend-request.schema.json +15 -0
  22. package/contracts/v1/schemas/manifest.schema.json +6 -3
  23. package/contracts/v1/schemas/surface-profile.schema.json +166 -0
  24. package/dist/codex-chatgpt-control-backend.mjs +3198 -340
  25. package/dist/codex-chatgpt-control.bundle.mjs +4454 -1555
  26. package/dist/src/backend/client.d.ts +26 -1
  27. package/dist/src/backend/client.js +23 -1
  28. package/dist/src/backend/protocol.d.ts +1 -1
  29. package/dist/src/backend/protocol.js +11 -0
  30. package/dist/src/backend/session.js +22 -0
  31. package/dist/src/browser/attach.d.ts +1 -0
  32. package/dist/src/browser/attach.js +3 -3
  33. package/dist/src/browser/clipboard.d.ts +11 -0
  34. package/dist/src/browser/clipboard.js +34 -7
  35. package/dist/src/browser/page-state.js +17 -4
  36. package/dist/src/client.d.ts +32 -1
  37. package/dist/src/client.js +72 -16
  38. package/dist/src/commands/artifacts.js +1 -7
  39. package/dist/src/commands/configuration.d.ts +15 -0
  40. package/dist/src/commands/configuration.js +674 -0
  41. package/dist/src/commands/context.js +3 -1
  42. package/dist/src/commands/conversation.d.ts +15 -0
  43. package/dist/src/commands/conversation.js +44 -0
  44. package/dist/src/commands/deadline.d.ts +8 -0
  45. package/dist/src/commands/deadline.js +14 -0
  46. package/dist/src/commands/doctor.js +47 -3
  47. package/dist/src/commands/experience.d.ts +12 -0
  48. package/dist/src/commands/experience.js +288 -0
  49. package/dist/src/commands/files.js +61 -13
  50. package/dist/src/commands/messages.d.ts +2 -1
  51. package/dist/src/commands/messages.js +349 -87
  52. package/dist/src/commands/modes.d.ts +4 -1
  53. package/dist/src/commands/modes.js +243 -44
  54. package/dist/src/commands/probes.d.ts +15 -0
  55. package/dist/src/commands/probes.js +64 -0
  56. package/dist/src/commands/registry.js +102 -8
  57. package/dist/src/commands/reports.js +1 -1
  58. package/dist/src/commands/response-actions.js +1 -7
  59. package/dist/src/commands/sequence.js +24 -1
  60. package/dist/src/commands/session.d.ts +2 -0
  61. package/dist/src/commands/session.js +50 -1
  62. package/dist/src/commands/threads.js +3 -31
  63. package/dist/src/commands/work.d.ts +6 -0
  64. package/dist/src/commands/work.js +338 -0
  65. package/dist/src/dom/generation-state.d.ts +7 -0
  66. package/dist/src/dom/generation-state.js +7 -1
  67. package/dist/src/dom/label-match.d.ts +3 -0
  68. package/dist/src/dom/label-match.js +25 -0
  69. package/dist/src/dom/locale/am.d.ts +8 -1
  70. package/dist/src/dom/locale/am.js +8 -1
  71. package/dist/src/dom/locale/ar.d.ts +9 -1
  72. package/dist/src/dom/locale/ar.js +9 -1
  73. package/dist/src/dom/locale/bg.d.ts +9 -1
  74. package/dist/src/dom/locale/bg.js +9 -1
  75. package/dist/src/dom/locale/bn.d.ts +9 -1
  76. package/dist/src/dom/locale/bn.js +9 -1
  77. package/dist/src/dom/locale/bs.d.ts +8 -1
  78. package/dist/src/dom/locale/bs.js +8 -1
  79. package/dist/src/dom/locale/ca.d.ts +8 -1
  80. package/dist/src/dom/locale/ca.js +8 -1
  81. package/dist/src/dom/locale/cs.d.ts +8 -1
  82. package/dist/src/dom/locale/cs.js +8 -1
  83. package/dist/src/dom/locale/da.d.ts +7 -1
  84. package/dist/src/dom/locale/da.js +7 -1
  85. package/dist/src/dom/locale/de.d.ts +10 -3
  86. package/dist/src/dom/locale/de.js +10 -3
  87. package/dist/src/dom/locale/el.d.ts +8 -1
  88. package/dist/src/dom/locale/el.js +8 -1
  89. package/dist/src/dom/locale/en.d.ts +40 -1
  90. package/dist/src/dom/locale/en.js +42 -1
  91. package/dist/src/dom/locale/es-419.d.ts +8 -1
  92. package/dist/src/dom/locale/es-419.js +8 -1
  93. package/dist/src/dom/locale/es-ES.d.ts +8 -1
  94. package/dist/src/dom/locale/es-ES.js +8 -1
  95. package/dist/src/dom/locale/et.d.ts +8 -1
  96. package/dist/src/dom/locale/et.js +8 -1
  97. package/dist/src/dom/locale/fa.d.ts +9 -1
  98. package/dist/src/dom/locale/fa.js +9 -1
  99. package/dist/src/dom/locale/fi.d.ts +8 -1
  100. package/dist/src/dom/locale/fi.js +8 -1
  101. package/dist/src/dom/locale/fr-CA.d.ts +8 -1
  102. package/dist/src/dom/locale/fr-CA.js +8 -1
  103. package/dist/src/dom/locale/fr-FR.d.ts +8 -3
  104. package/dist/src/dom/locale/fr-FR.js +8 -3
  105. package/dist/src/dom/locale/gu.d.ts +8 -1
  106. package/dist/src/dom/locale/gu.js +8 -1
  107. package/dist/src/dom/locale/hi.d.ts +8 -1
  108. package/dist/src/dom/locale/hi.js +8 -1
  109. package/dist/src/dom/locale/hr.d.ts +7 -1
  110. package/dist/src/dom/locale/hr.js +7 -1
  111. package/dist/src/dom/locale/hu.d.ts +8 -1
  112. package/dist/src/dom/locale/hu.js +8 -1
  113. package/dist/src/dom/locale/hy.d.ts +9 -1
  114. package/dist/src/dom/locale/hy.js +9 -1
  115. package/dist/src/dom/locale/id.d.ts +8 -1
  116. package/dist/src/dom/locale/id.js +8 -1
  117. package/dist/src/dom/locale/index.d.ts +20 -1
  118. package/dist/src/dom/locale/index.js +102 -0
  119. package/dist/src/dom/locale/is.d.ts +8 -1
  120. package/dist/src/dom/locale/is.js +8 -1
  121. package/dist/src/dom/locale/it.d.ts +8 -1
  122. package/dist/src/dom/locale/it.js +8 -1
  123. package/dist/src/dom/locale/ja.d.ts +8 -1
  124. package/dist/src/dom/locale/ja.js +8 -1
  125. package/dist/src/dom/locale/ka.d.ts +8 -1
  126. package/dist/src/dom/locale/ka.js +8 -1
  127. package/dist/src/dom/locale/kk.d.ts +8 -1
  128. package/dist/src/dom/locale/kk.js +8 -1
  129. package/dist/src/dom/locale/kn.d.ts +9 -1
  130. package/dist/src/dom/locale/kn.js +9 -1
  131. package/dist/src/dom/locale/ko.d.ts +8 -1
  132. package/dist/src/dom/locale/ko.js +8 -1
  133. package/dist/src/dom/locale/lt.d.ts +9 -1
  134. package/dist/src/dom/locale/lt.js +9 -1
  135. package/dist/src/dom/locale/lv.d.ts +8 -1
  136. package/dist/src/dom/locale/lv.js +8 -1
  137. package/dist/src/dom/locale/mk.d.ts +7 -1
  138. package/dist/src/dom/locale/mk.js +7 -1
  139. package/dist/src/dom/locale/ml.d.ts +9 -1
  140. package/dist/src/dom/locale/ml.js +9 -1
  141. package/dist/src/dom/locale/mn.d.ts +9 -1
  142. package/dist/src/dom/locale/mn.js +9 -1
  143. package/dist/src/dom/locale/mr.d.ts +9 -1
  144. package/dist/src/dom/locale/mr.js +9 -1
  145. package/dist/src/dom/locale/ms.d.ts +8 -1
  146. package/dist/src/dom/locale/ms.js +8 -1
  147. package/dist/src/dom/locale/my.d.ts +8 -1
  148. package/dist/src/dom/locale/my.js +8 -1
  149. package/dist/src/dom/locale/nb.d.ts +8 -1
  150. package/dist/src/dom/locale/nb.js +8 -1
  151. package/dist/src/dom/locale/nl.d.ts +8 -1
  152. package/dist/src/dom/locale/nl.js +8 -1
  153. package/dist/src/dom/locale/pa.d.ts +9 -1
  154. package/dist/src/dom/locale/pa.js +9 -1
  155. package/dist/src/dom/locale/pl.d.ts +8 -1
  156. package/dist/src/dom/locale/pl.js +8 -1
  157. package/dist/src/dom/locale/pt-BR.d.ts +8 -1
  158. package/dist/src/dom/locale/pt-BR.js +8 -1
  159. package/dist/src/dom/locale/pt-PT.d.ts +8 -1
  160. package/dist/src/dom/locale/pt-PT.js +8 -1
  161. package/dist/src/dom/locale/ro.d.ts +7 -1
  162. package/dist/src/dom/locale/ro.js +7 -1
  163. package/dist/src/dom/locale/ru.d.ts +5 -1
  164. package/dist/src/dom/locale/ru.js +5 -1
  165. package/dist/src/dom/locale/sk.d.ts +8 -1
  166. package/dist/src/dom/locale/sk.js +8 -1
  167. package/dist/src/dom/locale/sl.d.ts +8 -1
  168. package/dist/src/dom/locale/sl.js +8 -1
  169. package/dist/src/dom/locale/so.d.ts +8 -1
  170. package/dist/src/dom/locale/so.js +8 -1
  171. package/dist/src/dom/locale/sq.d.ts +8 -1
  172. package/dist/src/dom/locale/sq.js +8 -1
  173. package/dist/src/dom/locale/sr.d.ts +5 -1
  174. package/dist/src/dom/locale/sr.js +5 -1
  175. package/dist/src/dom/locale/sv.d.ts +8 -1
  176. package/dist/src/dom/locale/sv.js +8 -1
  177. package/dist/src/dom/locale/sw.d.ts +8 -1
  178. package/dist/src/dom/locale/sw.js +8 -1
  179. package/dist/src/dom/locale/ta.d.ts +9 -1
  180. package/dist/src/dom/locale/ta.js +9 -1
  181. package/dist/src/dom/locale/te.d.ts +9 -1
  182. package/dist/src/dom/locale/te.js +9 -1
  183. package/dist/src/dom/locale/th.d.ts +8 -1
  184. package/dist/src/dom/locale/th.js +8 -1
  185. package/dist/src/dom/locale/tl.d.ts +4 -5
  186. package/dist/src/dom/locale/tl.js +4 -5
  187. package/dist/src/dom/locale/tr.d.ts +8 -1
  188. package/dist/src/dom/locale/tr.js +8 -1
  189. package/dist/src/dom/locale/types.d.ts +25 -1
  190. package/dist/src/dom/locale/uk.d.ts +8 -1
  191. package/dist/src/dom/locale/uk.js +8 -1
  192. package/dist/src/dom/locale/ur.d.ts +8 -1
  193. package/dist/src/dom/locale/ur.js +8 -1
  194. package/dist/src/dom/locale/vi.d.ts +8 -1
  195. package/dist/src/dom/locale/vi.js +8 -1
  196. package/dist/src/dom/locale/zh-HK.d.ts +9 -1
  197. package/dist/src/dom/locale/zh-HK.js +10 -2
  198. package/dist/src/dom/locale/zh-Hans.d.ts +9 -1
  199. package/dist/src/dom/locale/zh-Hans.js +9 -1
  200. package/dist/src/dom/locale/zh-TW.d.ts +9 -1
  201. package/dist/src/dom/locale/zh-TW.js +9 -1
  202. package/dist/src/dom/menus.js +13 -2
  203. package/dist/src/dom/selectors.js +6 -1
  204. package/dist/src/dom/wait-snapshot.d.ts +43 -0
  205. package/dist/src/dom/wait-snapshot.js +127 -0
  206. package/dist/src/index.d.ts +3 -0
  207. package/dist/src/index.js +3 -0
  208. package/dist/src/runner/responses.d.ts +3 -1
  209. package/dist/src/runner/responses.js +16 -1
  210. package/dist/src/runner/result.js +129 -4
  211. package/dist/src/runner/stream.d.ts +1 -1
  212. package/dist/src/runner/stream.js +6 -0
  213. package/dist/src/runner/types.d.ts +35 -1
  214. package/dist/src/safety/risk.d.ts +11 -0
  215. package/dist/src/safety/risk.js +11 -0
  216. package/dist/src/scripts/apply-intelligence-locale-captures.d.ts +6 -0
  217. package/dist/src/scripts/apply-intelligence-locale-captures.js +147 -15
  218. package/dist/src/scripts/capture-intelligence-locales.d.ts +3 -0
  219. package/dist/src/scripts/capture-intelligence-locales.js +372 -3
  220. package/dist/src/scripts/capture-surface-profile.d.ts +23 -0
  221. package/dist/src/scripts/capture-surface-profile.js +304 -0
  222. package/dist/src/scripts/live-smoke/scenarios.js +34 -1
  223. package/dist/src/scripts/live-smoke.js +1 -0
  224. package/dist/src/types.d.ts +273 -2
  225. package/package.json +4 -3
  226. package/references/2026-07-16-chat-work-surfaces.md +87 -0
  227. package/references/agents-runner.md +13 -2
  228. package/references/backend-protocol.md +19 -7
  229. package/references/language-coverage.md +40 -3
  230. package/references/localization.md +48 -10
  231. package/references/python-parity.md +16 -4
  232. package/references/responses-adapter.md +12 -1
  233. package/references/troubleshooting.md +49 -6
@@ -7,8 +7,9 @@ const descriptors = [
7
7
  `await chatgpt.askInThread({ thread: { type: "search", query: "Naming macOS Utility" }, prompt: "Continue." });`,
8
8
  `await chatgpt.askInThread({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, prompt: "Continue." });`
9
9
  ]),
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: { model: "Pro" }, files: ["/absolute/host/path/brief.md"], prompt: "Summarize this.", wait: true, read: { format: "markdown" } });`
10
+ workflow("askWithFiles", "Attach absolute local file paths, optionally open/configure Chat or Work, ask, wait, and read.", [
11
+ `await chatgpt.askWithFiles({ thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true, experience: "chat", configuration: { intelligence: "Pro" }, files: ["/absolute/host/path/brief.md"], prompt: "Summarize this.", wait: true, read: { format: "markdown" } });`,
12
+ `await chatgpt.askWithFiles({ mode: { model: "Pro" }, files: ["/absolute/host/path/image.jpg"], prompt: "Describe this image.", wait: true });`
12
13
  ]),
13
14
  workflow("askAndDownload", "Ask ChatGPT to produce a visible downloadable output and save the latest exposed file.", [
14
15
  `await chatgpt.askAndDownload({ prompt: "Create a CSV.", download: { destDir: "/absolute/host/output" }, wait: true });`
@@ -16,13 +17,16 @@ const descriptors = [
16
17
  workflow("runMessages", "Run sequential prompts where later prompts can use earlier step data.", [
17
18
  `await chatgpt.runMessages({ messages: [{ id: "first", prompt: "alpha" }, { id: "second", prompt: "beta" }] });`
18
19
  ]),
19
- workflow("runner.run", "Agents-style facade: run a visible ChatGPT browser-control agent against input, files, thread, existing-tab, mode, and response options.", [
20
- `const agent = chatgpt.agent({ name: "reviewer", instructions: "Review deeply." }); await chatgpt.runner.run(agent, { input: "Review this.", thread: { type: "new" } });`,
20
+ workflow("runner.run", "Agents-style facade: run a visible ChatGPT agent against input, files, thread, existing-tab, experience, configuration, legacy mode, and response options.", [
21
+ `const agent = chatgpt.agent({ name: "reviewer", instructions: "Review deeply." }); await chatgpt.runner.run(agent, { input: "Review this.", thread: { type: "new" }, experience: "chat" });`,
21
22
  `await chatgpt.runner.run(agent, { input: "Continue.", thread: { type: "url", url: "https://chatgpt.com/c/<conversation-id>" }, existingTab: true });`
22
23
  ]),
23
24
  workflow("responses.create", "Narrow Responses-shaped adapter over the visible ChatGPT browser-control runner; rejects unsupported API-only fields before prompt submission.", [
24
25
  `await chatgpt.responses.create({ input: "Summarize.", thread: { type: "current" }, text: { format: "markdown" }, stream: false });`
25
26
  ]),
27
+ workflow("work.start", "Open Work, optionally apply model/effort/speed configuration, submit one task, and optionally wait/read without blind resubmission.", [
28
+ `await chatgpt.work.start({ prompt: "Analyze the attached package.", files: ["/absolute/path/package.tgz"], configuration: { model: "GPT-5.6 Sol", effort: "High", speed: "Fast" }, wait: true, read: { format: "markdown" } });`
29
+ ]),
26
30
  workflow("copyLatest", "Copy or DOM-read the latest assistant response with Markdown-first fidelity.", [
27
31
  `await chatgpt.copyLatest({ prefer: "clipboard" });`
28
32
  ]),
@@ -63,6 +67,14 @@ const descriptors = [
63
67
  `await chatgpt.createReport(result, { destDir: "/absolute/host/reports" });`
64
68
  ]),
65
69
  primitive("session.bootstrap", "Attach to ChatGPT in Chrome and detect login/blocker state.", 30000),
70
+ primitive("experience.detect", "Detect whether the scoped visible composer is Chat, Work, or unknown and return its selector profile.", 30000),
71
+ primitive("experience.open", "Open Chat or Work and verify the target surface from scoped composer evidence.", 30000),
72
+ primitive("configuration.inspect", "Inspect the active Chat or Work selector profile, axes, options, and selected values.", 30000),
73
+ primitive("configuration.apply", "Apply model/intelligence/effort/speed configuration one axis at a time and verify the final visible state.", 30000),
74
+ primitive("work.status", "Read current Work task progress and optionally enumerate visible artifacts.", 5000),
75
+ primitive("work.wait", "Wait for the current Work task response to stabilize.", 120000),
76
+ primitive("work.steer", "Submit a visible steering message to the current Work task without opening a new task.", 120000),
77
+ primitive("work.readLatest", "Read the latest response from the current Work task.", 30000),
66
78
  primitive("threads.new", "Open a new ChatGPT thread.", 30000),
67
79
  primitive("threads.search", "Search visible ChatGPT history by query.", 30000),
68
80
  primitive("threads.open", "Open a thread by URL, conversation id, title, or search result.", 30000),
@@ -71,6 +83,7 @@ const descriptors = [
71
83
  primitive("messages.ask", "Compose, submit, optionally wait, and optionally read.", 120000),
72
84
  primitive("messages.wait", "Wait for the latest assistant response to stabilize.", 120000),
73
85
  primitive("messages.readLatest", "Read the latest message as Markdown, normalized text, blocks, or HTML.", 30000),
86
+ primitive("messages.status", "Read a compact latest-assistant progress snapshot without treating partial text as complete.", 5000),
74
87
  primitive("messages.waitAndRead", "Wait for completion and read the latest message.", 120000),
75
88
  primitive("artifacts.listLatest", "Detect the latest visible generated ChatGPT artifact, such as an image-only result.", 30000),
76
89
  primitive("artifacts.wait", "Wait for a visible generated ChatGPT artifact to appear and stabilize.", 120000),
@@ -82,7 +95,8 @@ const descriptors = [
82
95
  primitive("projects.sources.planAdd", "Dry-run an append-only Project Sources file add from explicit local files without opening ChatGPT.", 30000),
83
96
  primitive("projects.sources.add", "Append explicit local files to a visible ChatGPT Project Sources list after confirmMutation: true.", 180000),
84
97
  primitive("response.copy", "Click Copy response and return clipboard Markdown, with DOM fallback.", 5000),
85
- primitive("modes.set", "Select a visible model, intelligence, effort, or nested model-version candidate when unambiguous.", 30000),
98
+ primitive("modes.set", "Legacy compatibility command: select a visible model/intelligence/effort/version candidate with historical warning-oriented verification.", 30000),
99
+ primitive("modes.get", "Legacy compatibility command: read mode labels shown on visible composer controls without changing them.", 30000),
86
100
  primitive("tools.select", "Select a visible ChatGPT tool when unambiguous.", 30000)
87
101
  ];
88
102
  export function commandDescriptors() {
@@ -190,12 +204,24 @@ function workflowArgs(name) {
190
204
  return { first: "first message", second: "second message" };
191
205
  if (name === "new-ask-read")
192
206
  return { prompt: "message to send" };
207
+ if (name === "work.start") {
208
+ return {
209
+ prompt: "visible Work task prompt",
210
+ newTask: "start from a blank Work task; defaults to true",
211
+ files: "optional absolute local file paths",
212
+ configuration: "optional Work model, effort, and speed values",
213
+ wait: "optional wait behavior",
214
+ read: "optional response capture behavior"
215
+ };
216
+ }
193
217
  if (name === "askWithFiles") {
194
218
  return {
195
219
  files: "absolute local file paths to attach before submitting",
196
220
  prompt: "message to send after files are attached",
197
221
  thread: "optional thread selector",
198
222
  existingTab: "true or explicit policy to claim a user-open Chrome tab instead of opening a replacement",
223
+ experience: "optional chat or work surface",
224
+ configuration: "optional strict visible configuration such as { intelligence: \"Pro\" } or Work model/effort/speed",
199
225
  mode: "optional visible mode selection, e.g. { model: \"Pro\" } or { intelligence: \"Pro\", modelVersion: \"5.4\" }",
200
226
  wait: "true or wait options; defaults to true",
201
227
  read: "true or read options such as { format: \"markdown\" }; defaults to Markdown",
@@ -206,6 +232,9 @@ function workflowArgs(name) {
206
232
  prompt: "message to send or workflow-specific input",
207
233
  thread: "optional thread selector",
208
234
  existingTab: "true or explicit policy to claim a user-open Chrome tab instead of opening a replacement",
235
+ experience: "optional chat or work surface",
236
+ configuration: "optional surface-aware visible configuration",
237
+ mode: "legacy visible mode preference",
209
238
  report: "optional redacted report settings"
210
239
  };
211
240
  }
@@ -232,8 +261,34 @@ function reportArgs(name) {
232
261
  return { result: "CommandResult to persist", destDir: "optional report directory" };
233
262
  }
234
263
  function primitiveArgs(name) {
264
+ if (name === "messages.wait")
265
+ return {
266
+ timeoutMs: "optional wait timeout",
267
+ stableMs: "optional stability window before completion",
268
+ pollMs: "optional polling interval",
269
+ mode: "normal or deep_research",
270
+ responseContent: "include for current behavior, metadata to omit assistant text and return chars/hash only"
271
+ };
235
272
  if (name === "messages.readLatest")
236
273
  return { role: "assistant or user", format: "markdown, normalized_text, visible_text, html, blocks, or all" };
274
+ if (name === "messages.status")
275
+ return { maxPreviewChars: "maximum latest-assistant preview characters to return; defaults to 240" };
276
+ if (name === "experience.detect")
277
+ return { timeoutMs: "optional timeout for attaching to the ChatGPT page" };
278
+ if (name === "experience.open")
279
+ return { experience: "chat or work", timeoutMs: "optional verified-switch timeout" };
280
+ if (name === "configuration.inspect")
281
+ return { experience: "optional expected chat or work surface", includeOptions: "open visible axis menus to enumerate options; defaults to true", timeoutMs: "optional inspection timeout" };
282
+ if (name === "configuration.apply")
283
+ return { experience: "optional target chat or work surface", desired: "model, intelligence, effort, speed, and/or modelVersion values", strict: "fail when visible postconditions are not verified; defaults to true", timeoutMs: "optional selection timeout" };
284
+ if (name === "work.status")
285
+ return { includeArtifacts: "include the latest visible artifact inventory; defaults to false for lightweight polling", maxPreviewChars: "maximum assistant preview characters" };
286
+ if (name === "work.wait")
287
+ return { timeoutMs: "optional Work wait timeout", stableMs: "required response stability window", pollMs: "polling interval" };
288
+ if (name === "work.steer")
289
+ return { prompt: "visible steering message for the current task", wait: "optional wait behavior", read: "optional response capture behavior" };
290
+ if (name === "work.readLatest")
291
+ return { format: "markdown, normalized_text, visible_text, html, blocks, or all", maxChars: "optional capture limit" };
237
292
  if (name === "artifacts.listLatest")
238
293
  return { kind: "artifact kind; currently image", max: "maximum artifacts to return" };
239
294
  if (name === "artifacts.wait")
@@ -245,7 +300,18 @@ function primitiveArgs(name) {
245
300
  if (name.startsWith("threads.search"))
246
301
  return { query: "history search query" };
247
302
  if (name === "files.preflight")
248
- return { paths: "absolute local file paths", maxBytesPerFile: "optional local per-file byte limit", maxTotalBytes: "optional local total byte limit" };
303
+ return {
304
+ paths: "absolute local file paths",
305
+ maxBytesPerFile: "optional local per-file byte limit",
306
+ maxTotalBytes: "optional local total byte limit",
307
+ includeHashes: "optional SHA-256 metadata for diagnostics; file contents are not returned"
308
+ };
309
+ if (name === "files.attach")
310
+ return {
311
+ paths: "absolute local file paths",
312
+ includeDiagnostics: "optional preflight and browser input file-size metadata",
313
+ includeHashes: "optional SHA-256 metadata inside diagnostics; file contents are not returned"
314
+ };
249
315
  if (name.startsWith("files.attach"))
250
316
  return { paths: "absolute local file paths" };
251
317
  if (name === "projects.sources.list")
@@ -264,6 +330,11 @@ function primitiveArgs(name) {
264
330
  timeoutMs: "optional timeout for opening and selecting the visible mode menu"
265
331
  };
266
332
  }
333
+ if (name === "modes.get") {
334
+ return {
335
+ timeoutMs: "optional timeout for attaching to the ChatGPT page"
336
+ };
337
+ }
267
338
  return {};
268
339
  }
269
340
  function primitiveExamples(name) {
@@ -275,9 +346,32 @@ function primitiveExamples(name) {
275
346
  `await chatgpt.askWithFiles({ mode: { model: "Pro" }, files: ["/absolute/host/path.jpg"], prompt: "Describe this image.", wait: true });`
276
347
  ];
277
348
  }
349
+ if (name === "modes.get") {
350
+ return [
351
+ `await chatgpt.modes.get();`,
352
+ `// Verify an expensive Pro consult before submitting: const current = await chatgpt.modes.get();`
353
+ ];
354
+ }
355
+ if (name === "experience.detect")
356
+ return [`await chatgpt.experience.detect();`];
357
+ if (name === "experience.open")
358
+ return [`await chatgpt.experience.open({ experience: "work" });`];
359
+ if (name === "configuration.inspect")
360
+ return [`await chatgpt.configuration.inspect({ experience: "work" });`];
361
+ if (name === "configuration.apply")
362
+ return [`await chatgpt.configuration.apply({ experience: "work", desired: { model: "GPT-5.6 Sol", effort: "High", speed: "Fast" }, strict: true });`];
363
+ if (name === "work.status")
364
+ return [`await chatgpt.work.status();`];
365
+ if (name === "work.wait")
366
+ return [`await chatgpt.work.wait({ timeoutMs: 600000 });`];
367
+ if (name === "work.steer")
368
+ return [`await chatgpt.work.steer({ prompt: "Focus the comparison on deployment ergonomics." });`];
369
+ if (name === "work.readLatest")
370
+ return [`await chatgpt.work.readLatest({ format: "markdown" });`];
278
371
  if (name === "files.preflight") {
279
372
  return [
280
- `await chatgpt.files.preflight({ paths: ["/absolute/host/path.md"] });`
373
+ `await chatgpt.files.preflight({ paths: ["/absolute/host/path.md"] });`,
374
+ `await chatgpt.files.preflight({ paths: ["/absolute/host/path.md"], includeHashes: true });`
281
375
  ];
282
376
  }
283
377
  if (name === "files.attach") {
@@ -313,7 +407,7 @@ function primitiveBlockers(name) {
313
407
  return ["browser_bridge_unavailable", "login_required", "selector_drift", "confirmation", "permission", "upload_failed"];
314
408
  if (name.startsWith("artifacts."))
315
409
  return ["browser_bridge_unavailable", "login_required", "artifact_unavailable", "artifact_selector_drift", "artifact_download_unavailable"];
316
- if (name.startsWith("modes.") || name.startsWith("tools."))
410
+ if (name.startsWith("modes.") || name.startsWith("tools.") || name.startsWith("experience.") || name.startsWith("configuration.") || name.startsWith("work."))
317
411
  return ["browser_bridge_unavailable", "login_required", "selector_drift"];
318
412
  return commonBlockers();
319
413
  }
@@ -23,7 +23,7 @@ export async function createRunReport(env, result, options = {}) {
23
23
  }, options);
24
24
  const report = {
25
25
  schemaVersion: 1,
26
- createdAt: new Date().toISOString(),
26
+ createdAt,
27
27
  includeContent,
28
28
  summary,
29
29
  steps: result.steps?.map(step => ({
@@ -6,7 +6,7 @@ import { readLatestMessage, readMessages } from "../dom/messages.js";
6
6
  import { copyResponseButtons } from "../dom/selectors.js";
7
7
  import { contextFromPage } from "./context.js";
8
8
  import { withCommandOutputText } from "./output.js";
9
- import { bootstrap } from "./session.js";
9
+ import { ensurePage } from "./session.js";
10
10
  export async function copyResponse(env, args = {}) {
11
11
  const boot = await ensurePage(env);
12
12
  if (!boot.ok) {
@@ -166,9 +166,3 @@ function mergeResponseMetadata(data, latest) {
166
166
  if (latest.sourcesAvailable !== undefined)
167
167
  data.sourcesAvailable = latest.sourcesAvailable;
168
168
  }
169
- async function ensurePage(env) {
170
- if (env.page !== undefined) {
171
- return resultOk({}, await contextFromPage(env.page));
172
- }
173
- return bootstrap(env, { preferExistingTab: true });
174
- }
@@ -1,12 +1,15 @@
1
1
  import { downloadLatestArtifact, listLatestArtifacts, waitForArtifact } from "./artifacts.js";
2
+ import { applyConfiguration, inspectConfiguration } from "./configuration.js";
3
+ import { detectExperience, openExperience } from "./experience.js";
2
4
  import { attachFiles, downloadLatestFile } from "./files.js";
3
5
  import { addProjectSources, buildProjectSourceAddPlan, listProjectSources } from "./project-sources.js";
4
- import { askMessage, composeMessage, readLatest, submitMessage, waitAndRead, waitForMessage } from "./messages.js";
6
+ import { askMessage, composeMessage, messageStatus, readLatest, submitMessage, waitAndRead, waitForMessage } from "./messages.js";
5
7
  import { copyResponse } from "./response-actions.js";
6
8
  import { bootstrap } from "./session.js";
7
9
  import { newThread, openThread, searchThreads } from "./threads.js";
8
10
  import { setMode, selectTool } from "./modes.js";
9
11
  import { withCommandOutputText } from "./output.js";
12
+ import { readLatestWork, startWork, steerWork, waitForWork, workStatus } from "./work.js";
10
13
  export const defaultSequencePolicy = {
11
14
  stopOnError: true,
12
15
  returnPartial: true,
@@ -43,6 +46,24 @@ export async function executeStep(step, env, previousResults) {
43
46
  switch (step.command) {
44
47
  case "session.bootstrap":
45
48
  return bootstrap(env, step.args);
49
+ case "experience.detect":
50
+ return detectExperience(env, step.args);
51
+ case "experience.open":
52
+ return openExperience(env, step.args);
53
+ case "configuration.inspect":
54
+ return inspectConfiguration(env, step.args);
55
+ case "configuration.apply":
56
+ return applyConfiguration(env, step.args);
57
+ case "work.start":
58
+ return startWork(env, step.args);
59
+ case "work.status":
60
+ return workStatus(env, step.args);
61
+ case "work.wait":
62
+ return waitForWork(env, step.args);
63
+ case "work.steer":
64
+ return steerWork(env, step.args);
65
+ case "work.readLatest":
66
+ return readLatestWork(env, step.args);
46
67
  case "threads.search":
47
68
  return searchThreads(env, step.args);
48
69
  case "threads.open":
@@ -59,6 +80,8 @@ export async function executeStep(step, env, previousResults) {
59
80
  return waitForMessage(env, step.args);
60
81
  case "messages.readLatest":
61
82
  return readLatest(env, step.args);
83
+ case "messages.status":
84
+ return messageStatus(env, step.args);
62
85
  case "messages.waitAndRead":
63
86
  return waitAndRead(env, step.args);
64
87
  case "artifacts.listLatest":
@@ -1,2 +1,4 @@
1
1
  import type { BootstrapArgs, BootstrapData, CommandResult, RuntimeEnv } from "../types.js";
2
2
  export declare function bootstrap(env: RuntimeEnv, args?: BootstrapArgs): Promise<CommandResult<BootstrapData>>;
3
+ export declare function ensurePage(env: RuntimeEnv): Promise<CommandResult<unknown>>;
4
+ export declare function verifyTabAffinity(env: RuntimeEnv): Promise<CommandResult<unknown> | undefined>;
@@ -1,4 +1,4 @@
1
- import { attachChatGPTBrowser } from "../browser/attach.js";
1
+ import { attachChatGPTBrowser, tabIdFromPage } from "../browser/attach.js";
2
2
  import { readPageState } from "../browser/page-state.js";
3
3
  import { resultError, resultOk } from "../errors.js";
4
4
  import { contextFromPage } from "./context.js";
@@ -7,6 +7,9 @@ export async function bootstrap(env, args = {}) {
7
7
  const attached = await attachChatGPTBrowser(env, args);
8
8
  env.browser = attached.browser;
9
9
  env.page = attached.page;
10
+ if (attached.tabId !== undefined) {
11
+ env.expectedTabId = attached.tabId;
12
+ }
10
13
  const state = await readPageState(attached.page);
11
14
  const data = {
12
15
  browserName: attached.browserName,
@@ -23,3 +26,49 @@ export async function bootstrap(env, args = {}) {
23
26
  return resultError(error instanceof Error ? error : new Error(String(error)));
24
27
  }
25
28
  }
29
+ export async function ensurePage(env) {
30
+ if (env.page === undefined) {
31
+ return bootstrap(env, { preferExistingTab: true });
32
+ }
33
+ const affinity = await verifyTabAffinity(env);
34
+ if (affinity !== undefined) {
35
+ return affinity;
36
+ }
37
+ return resultOk({}, await contextFromPage(env.page, tabContext(env)));
38
+ }
39
+ export async function verifyTabAffinity(env) {
40
+ if (env.expectedTabId === undefined || env.page === undefined) {
41
+ return undefined;
42
+ }
43
+ const actualTabId = tabIdFromPage(env.page);
44
+ if (actualTabId === env.expectedTabId) {
45
+ return undefined;
46
+ }
47
+ const code = actualTabId === undefined ? "tab_affinity_unverifiable" : "tab_affinity_lost";
48
+ const message = actualTabId === undefined
49
+ ? `ChatGPT command cannot verify it is still attached to expected tab ${env.expectedTabId}.`
50
+ : `ChatGPT command would run on tab ${actualTabId}, but the workflow expected tab ${env.expectedTabId}.`;
51
+ return {
52
+ ok: false,
53
+ status: "blocked",
54
+ warnings: [],
55
+ blocker: {
56
+ kind: "selector_drift",
57
+ code,
58
+ message,
59
+ remediation: [
60
+ {
61
+ label: "Reclaim the intended tab",
62
+ instruction: "Run session.bootstrap again with an exact existingTab target, or pass the correct page/tab to createChatGPT before retrying.",
63
+ userActionRequired: false
64
+ }
65
+ ],
66
+ resumable: false
67
+ },
68
+ context: await contextFromPage(env.page, tabContext(env, actualTabId))
69
+ };
70
+ }
71
+ function tabContext(env, actualTabId = tabIdFromPage(env.page)) {
72
+ const tabId = actualTabId ?? env.expectedTabId;
73
+ return tabId === undefined ? {} : { tabId };
74
+ }
@@ -1,11 +1,11 @@
1
1
  import { parseConversationId, readPageState } from "../browser/page-state.js";
2
2
  import { resultError, resultOk } from "../errors.js";
3
- import { countPageMessages, readLatestMessageText } from "../dom/messages.js";
4
3
  import { requiredLocator, searchChatsButton, searchChatsInput, newChatButton } from "../dom/selectors.js";
5
4
  import { anyLabelPattern, localeLabels } from "../dom/locale-labels.js";
6
5
  import { normalizeWhitespace, stripTags } from "../dom/visible-text.js";
7
6
  import { contextFromPage } from "./context.js";
8
- import { bootstrap } from "./session.js";
7
+ import { ensureConversationTarget } from "./conversation.js";
8
+ import { ensurePage } from "./session.js";
9
9
  const CHATGPT_HOME = "https://chatgpt.com/";
10
10
  export function extractThreadSearchResultsFromHtml(html) {
11
11
  const anchors = html.matchAll(/<a\b(?<attrs>[^>]*\bhref=["'](?<href>\/c\/[^"']+)["'][^>]*)>(?<body>[\s\S]*?)<\/a>/gi);
@@ -100,13 +100,7 @@ export async function openThread(env, args, previousResults) {
100
100
  context: await contextFromPage(page)
101
101
  };
102
102
  }
103
- if (target.href !== undefined && target.href.startsWith("/")) {
104
- await page.goto?.(new URL(target.href, CHATGPT_HOME).toString(), { waitUntil: "domcontentloaded", timeout: args.timeoutMs ?? 30000 });
105
- }
106
- else {
107
- await page.goto?.(target.href ?? target.url, { waitUntil: "domcontentloaded", timeout: args.timeoutMs ?? 30000 });
108
- }
109
- await waitForThreadHydrated(page, args.timeoutMs ?? 30000, parseConversationId(target.url));
103
+ await ensureConversationTarget(page, target, { timeoutMs: args.timeoutMs ?? 30000 });
110
104
  const state = await readPageState(page);
111
105
  return resultOk(openThreadData(state.url, state.conversationId, state.title ?? target.title), await contextFromPage(page));
112
106
  }
@@ -114,12 +108,6 @@ export async function openThread(env, args, previousResults) {
114
108
  return resultError(error instanceof Error ? error : new Error(String(error)), await contextFromPage(page));
115
109
  }
116
110
  }
117
- async function ensurePage(env) {
118
- if (env.page !== undefined) {
119
- return resultOk({}, await contextFromPage(env.page));
120
- }
121
- return bootstrap(env, { preferExistingTab: true });
122
- }
123
111
  async function resolveOpenTarget(env, args, previousResults) {
124
112
  if (args.url !== undefined) {
125
113
  return { url: args.url };
@@ -283,19 +271,3 @@ function filterResultsByQuery(results, query) {
283
271
  return haystack.includes(wanted) || wanted.includes(normalizeForMatch(result.title));
284
272
  });
285
273
  }
286
- async function waitForThreadHydrated(page, timeoutMs, expectedConversationId) {
287
- const started = Date.now();
288
- await page.waitForTimeout?.(1000);
289
- while (Date.now() - started < timeoutMs) {
290
- const url = typeof page.url === "function" ? await Promise.resolve(page.url()).catch(() => "") : "";
291
- const urlMatches = expectedConversationId === undefined || url.includes(expectedConversationId);
292
- const count = await countPageMessages(page).catch(() => 0);
293
- const latestAssistantText = await readLatestMessageText(page, "assistant").catch(() => undefined);
294
- const title = typeof page.title === "function" ? await page.title().catch(() => "") : "";
295
- if (urlMatches && ((latestAssistantText?.trim().length ?? 0) > 0 || (count > 0 && title.length > 0 && title !== "ChatGPT"))) {
296
- await page.waitForTimeout?.(250);
297
- return;
298
- }
299
- await page.waitForTimeout?.(500);
300
- }
301
- }
@@ -0,0 +1,6 @@
1
+ import type { CommandResult, ReadWorkLatestArgs, ReadWorkLatestData, RuntimeEnv, StartWorkArgs, StartWorkData, SteerWorkArgs, SteerWorkData, WorkStatusArgs, WorkStatusData, WorkWaitArgs, WorkWaitData } from "../types.js";
2
+ export declare function startWork(env: RuntimeEnv, args: StartWorkArgs): Promise<CommandResult<StartWorkData>>;
3
+ export declare function workStatus(env: RuntimeEnv, args?: WorkStatusArgs): Promise<CommandResult<WorkStatusData>>;
4
+ export declare function waitForWork(env: RuntimeEnv, args?: WorkWaitArgs): Promise<CommandResult<WorkWaitData>>;
5
+ export declare function steerWork(env: RuntimeEnv, args: SteerWorkArgs): Promise<CommandResult<SteerWorkData>>;
6
+ export declare function readLatestWork(env: RuntimeEnv, args?: ReadWorkLatestArgs): Promise<CommandResult<ReadWorkLatestData>>;