codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.4

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 (231) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +43 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +40 -1
  4. package/contracts/v1/fixtures/backend-compatibility.json +21 -0
  5. package/contracts/v1/fixtures/backend-version.json +6 -1
  6. package/contracts/v1/fixtures/download-blocked-by-browser.json +22 -0
  7. package/contracts/v1/fixtures/download-receipt-failed.json +22 -0
  8. package/contracts/v1/fixtures/download-receipt-timeout.json +22 -0
  9. package/contracts/v1/fixtures/journal-rpc-indeterminate.json +32 -0
  10. package/contracts/v1/fixtures/journal-rpc-unsupported-platform.json +32 -0
  11. package/contracts/v1/fixtures/journal-runtime-unavailable.json +32 -0
  12. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  13. package/contracts/v1/fixtures/operation-action.json +13 -0
  14. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  15. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  16. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  17. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  18. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  19. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  20. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  21. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  22. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  23. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  24. package/contracts/v1/fixtures/operation-event.json +18 -0
  25. package/contracts/v1/fixtures/operation-handle.json +10 -0
  26. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  27. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  28. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  29. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  30. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  31. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  32. package/contracts/v1/fixtures/operation-request.json +33 -0
  33. package/contracts/v1/fixtures/operation-state.json +144 -0
  34. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  35. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  36. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  37. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  38. package/contracts/v1/manifest.json +185 -1
  39. package/contracts/v1/parity-suite.json +106 -8
  40. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  41. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  42. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  43. package/contracts/v1/schemas/manifest.schema.json +57 -0
  44. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  45. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  46. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  47. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  48. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  49. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  50. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  51. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  52. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  53. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  54. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  55. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  56. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  57. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  58. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  59. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  60. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  61. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  62. package/contracts/v1/surface-drift-policy.json +50 -0
  63. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  64. package/dist/codex-chatgpt-control-backend.mjs +45882 -9361
  65. package/dist/codex-chatgpt-control-journal.mjs +5547 -0
  66. package/dist/codex-chatgpt-control-live-smoke.bundle.mjs +51677 -0
  67. package/dist/codex-chatgpt-control-release-canary.bundle.mjs +52104 -0
  68. package/dist/codex-chatgpt-control.bundle.mjs +48392 -10260
  69. package/dist/src/backend/client.d.ts +103 -2
  70. package/dist/src/backend/client.js +1436 -97
  71. package/dist/src/backend/compatibility.d.ts +12 -0
  72. package/dist/src/backend/compatibility.js +208 -0
  73. package/dist/src/backend/protocol.d.ts +84 -1
  74. package/dist/src/backend/protocol.js +31 -5
  75. package/dist/src/backend/runtime-identity.d.ts +10 -0
  76. package/dist/src/backend/runtime-identity.js +141 -0
  77. package/dist/src/backend/session.d.ts +10 -2
  78. package/dist/src/backend/session.js +478 -7
  79. package/dist/src/backend/stdio-server.d.ts +4 -1
  80. package/dist/src/backend/stdio-server.js +240 -28
  81. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  82. package/dist/src/browser/active-composer-file-input.js +33 -0
  83. package/dist/src/browser/attach.d.ts +4 -1
  84. package/dist/src/browser/attach.js +136 -28
  85. package/dist/src/browser/chatgpt-url.d.ts +2 -0
  86. package/dist/src/browser/chatgpt-url.js +9 -0
  87. package/dist/src/browser/downloads.d.ts +17 -1
  88. package/dist/src/browser/downloads.js +150 -28
  89. package/dist/src/browser/page-state.js +6 -0
  90. package/dist/src/client.d.ts +43 -0
  91. package/dist/src/client.js +1279 -59
  92. package/dist/src/commands/artifacts.js +6 -4
  93. package/dist/src/commands/chat-popover.d.ts +37 -0
  94. package/dist/src/commands/chat-popover.js +537 -0
  95. package/dist/src/commands/configuration.d.ts +5 -0
  96. package/dist/src/commands/configuration.js +134 -21
  97. package/dist/src/commands/context.js +10 -3
  98. package/dist/src/commands/doctor.d.ts +1 -1
  99. package/dist/src/commands/doctor.js +27 -4
  100. package/dist/src/commands/experience.d.ts +5 -0
  101. package/dist/src/commands/experience.js +104 -21
  102. package/dist/src/commands/files.js +14 -33
  103. package/dist/src/commands/modes.js +85 -21
  104. package/dist/src/commands/power-discovery.d.ts +130 -0
  105. package/dist/src/commands/power-discovery.js +669 -0
  106. package/dist/src/commands/project-sources.js +325 -31
  107. package/dist/src/commands/sequence.js +59 -32
  108. package/dist/src/commands/session.js +19 -6
  109. package/dist/src/commands/work.js +31 -0
  110. package/dist/src/dom/composer-text.d.ts +6 -0
  111. package/dist/src/dom/composer-text.js +85 -0
  112. package/dist/src/errors.d.ts +9 -0
  113. package/dist/src/errors.js +22 -0
  114. package/dist/src/index.d.ts +6 -0
  115. package/dist/src/index.js +9 -0
  116. package/dist/src/operations/artifact-output.d.ts +69 -0
  117. package/dist/src/operations/artifact-output.js +1748 -0
  118. package/dist/src/operations/artifact-stream.d.ts +19 -0
  119. package/dist/src/operations/artifact-stream.js +35 -0
  120. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  121. package/dist/src/operations/artifact-transfer.js +1312 -0
  122. package/dist/src/operations/browser-adapter.d.ts +167 -0
  123. package/dist/src/operations/browser-adapter.js +2103 -0
  124. package/dist/src/operations/browser-observation.d.ts +110 -0
  125. package/dist/src/operations/browser-observation.js +1256 -0
  126. package/dist/src/operations/browser-target.d.ts +105 -0
  127. package/dist/src/operations/browser-target.js +531 -0
  128. package/dist/src/operations/canonical.d.ts +4 -0
  129. package/dist/src/operations/canonical.js +412 -0
  130. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  131. package/dist/src/operations/chatgpt-runtime.js +1232 -0
  132. package/dist/src/operations/client.d.ts +138 -0
  133. package/dist/src/operations/client.js +1145 -0
  134. package/dist/src/operations/collector.d.ts +195 -0
  135. package/dist/src/operations/collector.js +1039 -0
  136. package/dist/src/operations/configuration-routing.d.ts +4 -0
  137. package/dist/src/operations/configuration-routing.js +14 -0
  138. package/dist/src/operations/control.d.ts +372 -0
  139. package/dist/src/operations/control.js +1498 -0
  140. package/dist/src/operations/file-identity.d.ts +33 -0
  141. package/dist/src/operations/file-identity.js +153 -0
  142. package/dist/src/operations/handle.d.ts +19 -0
  143. package/dist/src/operations/handle.js +727 -0
  144. package/dist/src/operations/index.d.ts +25 -0
  145. package/dist/src/operations/index.js +25 -0
  146. package/dist/src/operations/journal-authority.d.ts +12 -0
  147. package/dist/src/operations/journal-authority.js +1 -0
  148. package/dist/src/operations/journal-rpc-client.d.ts +12 -0
  149. package/dist/src/operations/journal-rpc-client.js +225 -0
  150. package/dist/src/operations/journal-rpc-protocol.d.ts +32 -0
  151. package/dist/src/operations/journal-rpc-protocol.js +214 -0
  152. package/dist/src/operations/journal-rpc-server.d.ts +15 -0
  153. package/dist/src/operations/journal-rpc-server.js +272 -0
  154. package/dist/src/operations/journal.d.ts +143 -0
  155. package/dist/src/operations/journal.js +1957 -0
  156. package/dist/src/operations/production-attachments.d.ts +118 -0
  157. package/dist/src/operations/production-attachments.js +1035 -0
  158. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  159. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  160. package/dist/src/operations/production-chatgpt-attachments.d.ts +80 -0
  161. package/dist/src/operations/production-chatgpt-attachments.js +1639 -0
  162. package/dist/src/operations/production-configuration.d.ts +42 -0
  163. package/dist/src/operations/production-configuration.js +1544 -0
  164. package/dist/src/operations/production-primitives.d.ts +53 -0
  165. package/dist/src/operations/production-primitives.js +856 -0
  166. package/dist/src/operations/production-work-steer.d.ts +203 -0
  167. package/dist/src/operations/production-work-steer.js +1218 -0
  168. package/dist/src/operations/recovery.d.ts +80 -0
  169. package/dist/src/operations/recovery.js +174 -0
  170. package/dist/src/operations/runtime-adapter.d.ts +126 -0
  171. package/dist/src/operations/runtime-adapter.js +802 -0
  172. package/dist/src/operations/send-once.d.ts +224 -0
  173. package/dist/src/operations/send-once.js +1081 -0
  174. package/dist/src/operations/service.d.ts +286 -0
  175. package/dist/src/operations/service.js +2831 -0
  176. package/dist/src/operations/staging.d.ts +136 -0
  177. package/dist/src/operations/staging.js +630 -0
  178. package/dist/src/operations/state-machine.d.ts +29 -0
  179. package/dist/src/operations/state-machine.js +2047 -0
  180. package/dist/src/operations/submission.d.ts +423 -0
  181. package/dist/src/operations/submission.js +1676 -0
  182. package/dist/src/operations/transactional-chat-power.d.ts +17 -0
  183. package/dist/src/operations/transactional-chat-power.js +212 -0
  184. package/dist/src/operations/turn-ownership.d.ts +202 -0
  185. package/dist/src/operations/turn-ownership.js +700 -0
  186. package/dist/src/operations/types.d.ts +454 -0
  187. package/dist/src/operations/types.js +20 -0
  188. package/dist/src/operations/wire-requests.d.ts +21 -0
  189. package/dist/src/operations/wire-requests.js +396 -0
  190. package/dist/src/operations/wire-results.d.ts +97 -0
  191. package/dist/src/operations/wire-results.js +818 -0
  192. package/dist/src/runner/responses.d.ts +3 -1
  193. package/dist/src/runner/responses.js +19 -1
  194. package/dist/src/runner/result.js +227 -55
  195. package/dist/src/runner/types.d.ts +12 -0
  196. package/dist/src/runtime/command-routing.d.ts +149 -0
  197. package/dist/src/runtime/command-routing.js +431 -0
  198. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  199. package/dist/src/runtime/coordinated-browser.js +315 -0
  200. package/dist/src/runtime/coordinated-page.d.ts +41 -0
  201. package/dist/src/runtime/coordinated-page.js +651 -0
  202. package/dist/src/runtime/operation-context.d.ts +142 -0
  203. package/dist/src/runtime/operation-context.js +410 -0
  204. package/dist/src/runtime/runtime-session.d.ts +95 -0
  205. package/dist/src/runtime/runtime-session.js +314 -0
  206. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  207. package/dist/src/runtime/tab-coordinator.js +1200 -0
  208. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  209. package/dist/src/runtime/value-boundaries.js +49 -0
  210. package/dist/src/safety/blockers.js +8 -1
  211. package/dist/src/safety/untrusted-output.js +2 -1
  212. package/dist/src/scripts/backend-server.js +4 -1
  213. package/dist/src/scripts/capture-surface-profile.js +7 -2
  214. package/dist/src/scripts/journal-server.d.ts +2 -0
  215. package/dist/src/scripts/journal-server.js +37 -0
  216. package/dist/src/scripts/live-smoke/harness.js +79 -7
  217. package/dist/src/scripts/live-smoke/scenarios.d.ts +42 -1
  218. package/dist/src/scripts/live-smoke/scenarios.js +147 -30
  219. package/dist/src/scripts/live-smoke/transactional.d.ts +17 -0
  220. package/dist/src/scripts/live-smoke/transactional.js +254 -0
  221. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  222. package/dist/src/scripts/release-canary-module.js +17 -4
  223. package/dist/src/types.d.ts +38 -1
  224. package/package.json +8 -3
  225. package/references/2026-08-16-transactional-operations.md +459 -0
  226. package/references/2026-09-06-journal-service.md +140 -0
  227. package/references/agents-runner.md +34 -0
  228. package/references/backend-protocol.md +176 -0
  229. package/references/python-parity.md +137 -0
  230. package/references/responses-adapter.md +19 -0
  231. package/references/streaming.md +6 -0
@@ -108,6 +108,43 @@ The final event contains a normal runner result:
108
108
 
109
109
  Streaming is milestone streaming only. It does not promise token deltas or OpenAI API stream-event parity. Partial assistant text is emitted as `message_in_progress`; only completion-confirmed output is emitted as `message_completed`.
110
110
 
111
+ Persistent clients perform one single-flight `backend.hello` negotiation before
112
+ admitting multiplexed work. When the backend advertises request-ID-scoped unary
113
+ and stream multiplexing, one lifecycle-owned reader routes every response and
114
+ event to its exact `requestId`; compatible older backends remain single-flight.
115
+ Cancellation and timeout retain a bounded late-output tombstone. A record for a
116
+ known tombstone is drained, while an unknown request ID quarantines and recycles
117
+ the connection rather than being guessed into an active route.
118
+
119
+ The transport retains a bounded, redacted `backend_compatibility.v1` snapshot for
120
+ the current backend generation. Protocol or capability incompatibility rejects
121
+ the hello before any browser command is admitted. Compatible package, runtime,
122
+ and build differences are warnings rather than an exact package-version gate;
123
+ the report includes a `build_digest_mismatch` warning even when package versions
124
+ match. Missing provenance is reported as `unknown`, never inferred, and the
125
+ snapshot contains no command list, prompts, paths, secrets, or provider output.
126
+
127
+ Backpressure is bounded by both event count and encoded UTF-8 bytes. Overflow
128
+ fails only the affected stream route and leaves unrelated correlated routes
129
+ usable. The Node and Python clients bound aggregate queued stdin frames by
130
+ count and bytes, and both clients bound aggregate caller/control route
131
+ admissions with `maxInFlight`/`max_in_flight` (default `256`, minimum `2`) across
132
+ handshake probes, waiting legacy slots, pending unary routes, async
133
+ pre-reservations, and streams. During negotiation gaps where the handshake is
134
+ unknown/in progress and no control route is currently charged, caller
135
+ reservations use at most `maxInFlight - 1` slots so the transport can always
136
+ admit the next hello/legacy control probe. Once a control route is charged, it
137
+ counts as one ordinary live route and callers can use the full configured
138
+ bound; the virtual headroom returns between sequential legacy probes. Saturation
139
+ is rejected before request-ID
140
+ reservation and every terminal, cancellation, timeout, queued-never-started
141
+ release, and recycle path releases its live slot. The client rechecks route
142
+ ownership immediately before writing, so a request canceled while queued is never written later. If a started stdin write
143
+ does not settle, the child is recycled; at most one unresolved generation may
144
+ be detached, and a second unresolved generation fails closed until teardown
145
+ settles. These are transport liveness guards, not proof that a browser mutation
146
+ did or did not occur.
147
+
111
148
  ## Required Backend Commands
112
149
 
113
150
  The backend must support:
@@ -129,6 +166,68 @@ timeout Work result is recovered through status/wait/read on the same task, not
129
166
  by resubmitting the original prompt.
130
167
 
131
168
  `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.
169
+ The additive `compatibility` check is browser-free and exposes the retained
170
+ report in `details`; warning and unknown provenance map to an `unknown` check,
171
+ while a rejected negotiation maps to `blocked` and makes the report not ready.
172
+
173
+ ## Transactional operations (v1)
174
+
175
+ The additive operation surface uses four strict backend commands:
176
+ `operations.submit`, `operations.collect`, `operations.inspect`, and
177
+ `operations.control`. Their request and result schemas, caller-owned
178
+ `operationId` rules, fresh-handle recovery, and privacy boundary are defined in
179
+ [the transactional operations reference](2026-08-16-transactional-operations.md).
180
+
181
+ These commands are not aliases for `messages.submit`, `messages.wait`, or the
182
+ legacy workflow runner. `operations.submit` creates/reconciles one journal
183
+ record and returns an accepted/completed/blocked/uncertain envelope;
184
+ `operations.collect` observes only that operation's owned turn;
185
+ `operations.inspect` is browser-free durable inspection; and `operations.control`
186
+ binds one Stop or Work steer to a generating parent handle. `operations.run`
187
+ is an SDK composition, not a fifth wire command.
188
+
189
+ `operations.inspect` is browser-free and may include the same additive
190
+ `compatibility` report projected from the lifecycle-owned transport. It does not
191
+ reopen a tab or perform a browser read.
192
+
193
+ Transactional wire validation rejects unsupported fields before browser use.
194
+ The backend redacts adapter/journal failures at the protocol boundary so raw
195
+ prompts, local paths, URLs, and provider-private diagnostics do not cross the
196
+ NDJSON response. With no custom adapter seam, the default client constructs a
197
+ lazy request-local ChatGPT adapter after journal admission and fails closed if
198
+ the bridge, authenticated target evidence, or required provider primitive is
199
+ unavailable. A custom adapter configuration must supply the complete adapter
200
+ factory set; neither path falls back to a legacy sequence.
201
+
202
+ The default local journal requires real process identity, platform-appropriate
203
+ file ownership, and process-liveness authority. It validates those host
204
+ capabilities before creating journal directories or touching the browser. When
205
+ they are unavailable and no explicit journal service is configured, an
206
+ operation-aware high-level `ask` returns the normal command
207
+ result shape with `status: "blocked"`, `blocker.kind: "unknown"`,
208
+ `blocker.code: "journal_runtime_unavailable"`, `blocker.resumable: false`, and
209
+ `error.recoverable: false`. The bounded remediation uses the established
210
+ `label`, `instruction`, and `userActionRequired` fields. This is an unavailable
211
+ runtime result, not evidence of composer drift or a successful submission.
212
+ The shared `journal-runtime-unavailable.json` fixture is generated through the
213
+ public facade and preserves that result across Python decoding.
214
+
215
+ As verified on 2026-09-06, the restricted Codex JavaScript host used for issue
216
+ 41 exposes its browser bridge but does not expose the required process APIs.
217
+ It can run compatibility workflows, but its default local journal is
218
+ unavailable. An explicitly started normal Node journal service supplies durable
219
+ storage and signing through the authenticated private-file connection while
220
+ browser actions remain in the active bridge host. Configure
221
+ `operations.journalService.descriptorPath` as described in the
222
+ [journal service runbook](2026-09-06-journal-service.md). A plain Node subprocess
223
+ still cannot inherit the browser bridge. Do not substitute PID or owner values,
224
+ install a fake `process`, disable ownership checks, or infer that an unknown
225
+ lock owner is dead. An automatic retry or a new operation ID cannot repair a
226
+ missing host capability.
227
+
228
+ `operations.collect` optionally accepts `pollIntervalMs`, an integer from `0`
229
+ through `60000`. It controls only the interval between bounded observation
230
+ attempts. Poll sleeps occur outside browser/tab transactions.
132
231
 
133
232
  ## Host-Local Attachment Paths
134
233
 
@@ -185,6 +284,29 @@ reported as structured blockers such as
185
284
  `artifact_unavailable`, `artifact_selector_drift`, or
186
285
  `artifact_download_unavailable`, not protocol errors.
187
286
 
287
+ After a visible download activation, the runtime requires the browser bridge's
288
+ native completion receipt within the operation deadline. An unverified
289
+ completion returns `download_receipt_timeout` with kind `download_unavailable`,
290
+ status `blocked`, and nonresumable/nonrecoverable flags. It does not retry the
291
+ activation or switch download strategies, and preserves existing browser
292
+ downloads. A bridge that cannot expose a native receipt remains unsupported
293
+ for verified download completion.
294
+
295
+ A structurally identified Chrome error page with the exact
296
+ `ERR_BLOCKED_BY_CLIENT` code returns `download_blocked_by_browser` with the same
297
+ `download_unavailable` kind, blocked status, and nonresumable/nonrecoverable
298
+ flags. Text mentioning that code in a normal conversation is not browser-error
299
+ evidence. The runtime does not retry the activation or bypass browser
300
+ restrictions; the caller receives no successful download receipt and completion
301
+ remains unverified. This browser-error observation is distinct from a native
302
+ receipt timeout.
303
+
304
+ Other native event or activation failures return `download_receipt_failed`
305
+ with the same blocked and nonretryable semantics. A fixed message replaces
306
+ native error details, which may include sensitive download URLs. The runtime
307
+ supplies no successful receipt and does not switch to another strategy after
308
+ an uncertain activation.
309
+
188
310
  `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:
189
311
 
190
312
  ```json
@@ -261,6 +383,56 @@ python scripts/live_smoke.py --mode ordinary-shell
261
383
 
262
384
  In an ordinary shell without Codex browser bridge access, browser-required commands must return a structured `browser_bridge_unavailable` blocker. This is a successful smoke result when the backend process stays alive and protocol calls such as `backend.health` and `commands` succeed.
263
385
 
386
+ ## Transactional Live Qualification
387
+
388
+ Compatibility live smokes and release canaries do not qualify submit-once
389
+ behavior. The additive Node live scenario `transactional-submit-once` is
390
+ explicitly enabled with `CHATGPT_E2E_TRANSACTIONAL=1`; select that scenario
391
+ alone when using `CHATGPT_E2E_SCENARIOS` or the live-smoke module's scenario
392
+ filter. In a supported host with an already initialized browser bridge:
393
+
394
+ ```javascript
395
+ const selected = smoke.filterScenarios(smoke.optionalScenarios, "transactional-submit-once");
396
+ const run = await smoke.runLiveSmoke({
397
+ agent,
398
+ browser,
399
+ reportDir: "/absolute/path/to/local/reports/live-smoke",
400
+ env: {
401
+ CHATGPT_E2E_TRANSACTIONAL: "1",
402
+ // Optional when the browser host requires the separate journal service.
403
+ CHATGPT_E2E_JOURNAL_DESCRIPTOR: "/absolute/private/session/connection.json"
404
+ }
405
+ }, selected);
406
+ const qualified = run.results.length === 1 && run.results[0].status === "pass";
407
+ ```
408
+
409
+ Here `smoke` is the imported `codex-chatgpt-control-live-smoke.bundle.mjs`.
410
+ The scenario creates one synthetic Chat conversation using the public `ask`
411
+ facade and the default production runtime, with a fresh UUID, zero files, and a
412
+ multiline prompt. It performs submit-only, bounded collect, owned-receipt
413
+ verification, and a same-ID replay only after a completed receipt. It verifies
414
+ one satisfied Send action, the same operation/request/target handle, and exactly
415
+ one user/assistant exchange in the same established conversation before and
416
+ after replay. For `ambiguous_submit` only, it may make one observation-only
417
+ same-ID recovery after inspection proves the matching target and a durable
418
+ `observe_only_after_intent` Send action at the `send_may_have_occurred` boundary.
419
+ It then verifies the same action and intent survived recovery. Other blockers
420
+ stop qualification; there is no blind resubmission or replacement operation ID.
421
+ Reports contain only structural evidence, response length/hash, and fixed
422
+ status codes.
423
+
424
+ This scenario is optional, so inspect its own `status`; an empty
425
+ `requiredFailures` list does not establish a pass. A missing host capability,
426
+ pending collect, absent receipt, or changed turn count fails qualification.
427
+ Without the explicit service, the restricted Codex host described above fails
428
+ with `journal_runtime_unavailable`. Configuring the service makes that host
429
+ eligible for this qualification; the scenario must still pass submit, collect,
430
+ receipt and replay checks. A successful journal connection alone does not
431
+ qualify browser behavior. Python fixture and facade checks validate result
432
+ preservation; Python operation-aware relay qualification remains a separate
433
+ gate requiring a live bridge-hosted Node backend and a permitted transport to
434
+ that backend.
435
+
264
436
  ## Browser-Bridge Smoke
265
437
 
266
438
  Run only when intentionally operating a backend with live browser access:
@@ -355,3 +527,7 @@ npm run test:backend-conformance
355
527
  ```
356
528
 
357
529
  Python must also load and round-trip the same fixtures through Pydantic models. Any future backend implementation should pass these fixtures before claiming compatibility.
530
+
531
+ ## Restricted-host journal authority
532
+
533
+ The Node backend can use an explicit asynchronous journal service while keeping browser control in its active host. See [the journal service runbook](2026-09-06-journal-service.md) for startup, recovery, transport boundaries, and the intentional TypeScript host API asymmetry. Operation wire shapes and Python facades are unchanged. The private-file transport rejects Windows with `journal_rpc_unsupported_platform` before filesystem access; the existing local Node journal path is unchanged.
@@ -30,6 +30,98 @@ Wire fields stay TypeScript-compatible. Python exposes idiomatic aliases:
30
30
  | `newTask` | `new_task` |
31
31
  | `includeArtifacts` | `include_artifacts` |
32
32
 
33
+ Negotiated compatibility is also shared contract behavior. The Node and Python
34
+ transports retain a bounded `BackendCompatibilityReport` for the current
35
+ backend generation and expose it through `compatibility_report()` (or the
36
+ TypeScript equivalent). Protocol/capability rejection blocks before browser
37
+ commands; package, runtime, and build differences are precise warnings, not an
38
+ exact package-version requirement. A matching package version with a different
39
+ build digest remains a `build_digest_mismatch` warning. Unknown provenance stays
40
+ unknown. The report is redacted and contains no command list, prompt, path,
41
+ secret, or provider output.
42
+
43
+ ## Transactional operations parity
44
+
45
+ The direct v1 operation surface is shared by TypeScript and Python through the
46
+ same strict wire schemas and backend commands:
47
+
48
+ - `operations.submit`
49
+ - `operations.collect`
50
+ - `operations.inspect`
51
+ - `operations.control`
52
+
53
+ Python exposes sync `OperationsClient` and async `AsyncOperationsClient` on the
54
+ `ChatGPT`/`AsyncChatGPT` facades. Their keyword aliases are idiomatic
55
+ snake_case (`operation_id`, `response_content`, `timeout_ms`,
56
+ `control_action_id`), but `to_wire()` always emits the TypeScript-compatible
57
+ camelCase fields. Every envelope validates operation ID, request digest, fresh
58
+ handle, target binding, receipt/blocker identity, and mutation-boundary
59
+ monotonicity before returning to the caller. `run()` is local composition of
60
+ one submit and at most one collect; it is not a fifth backend command.
61
+ `operations.inspect` projects the transport compatibility snapshot into its
62
+ additive `compatibility` field without browser access. `doctor` exposes the same
63
+ report in its browser-free `compatibility` check; a warning or unknown
64
+ provenance is diagnostic and does not block readiness, while rejected
65
+ negotiation is blocked.
66
+
67
+ The immutable submit capture contract is exposed as
68
+ `OperationDurableCapturePolicy` (with the `OperationCapturePolicyState` alias)
69
+ and is serialized as the path-free `capturePolicy` object on created/state
70
+ records. It contains only `responseContent`, `responseFormat` (defaulting to
71
+ `markdown`), and `artifacts`; request-local `outputDirectory` is never accepted
72
+ on a durable model. A recovered `transfer` policy remains a transfer
73
+ obligation until a new request-local destination is explicitly authorized.
74
+
75
+ This direct parity does not make Python a second browser runtime. The current
76
+ backend remains Node-backed and, with no custom seam, creates the same lazy
77
+ request-local ChatGPT adapter as the TypeScript client. Browser-touching calls
78
+ still require a bridge-enabled runtime, authenticated target evidence, and the
79
+ required provider primitives. `inspect` is browser-free; an ordinary
80
+ Python-spawned Node process cannot inherit Codex's in-process bridge, so
81
+ ordinary-shell checks can exercise request/result validation and structured
82
+ blockers without claiming live ChatGPT control. Raw
83
+ `liveResponse` content is explicitly ephemeral and is never accepted into a
84
+ durable receipt or operation state.
85
+
86
+ Operation-aware Python `ask` preserves the Node host blocker
87
+ `journal_runtime_unavailable` as a `CommandResult` with `status == "blocked"`,
88
+ `blocker["kind"] == "unknown"`, `blocker["resumable"] == False`, and
89
+ `error["recoverable"] == False`, including the existing remediation fields.
90
+ Sync and async facades return it after one backend request; they do not replace
91
+ it with transport uncertainty, retry, or open another operation. The shared
92
+ `journal-runtime-unavailable.json` fixture covers exact wire round-tripping.
93
+ No Python model or browser implementation change is needed because the
94
+ TypeScript journal remains authoritative.
95
+
96
+ The default local journal requires real process identity, platform-appropriate
97
+ ownership, and process-liveness APIs before filesystem or browser mutation.
98
+ The restricted Codex JavaScript host observed on 2026-09-06 lacks those APIs
99
+ even though its browser bridge works. Configure the explicit journal service
100
+ on its bridge-hosted Node `BackendSession` to provide storage and signing from
101
+ a normal Node process. The Python facade and operation protocol stay unchanged.
102
+ A Python-to-Node relay alone cannot supply journal authority, and a plain Node
103
+ subprocess cannot inherit the browser bridge. Do not spoof process identity,
104
+ weaken ownership/lock checks, or retry with a new UUID.
105
+
106
+ The TypeScript and Python high-level Runner and Responses adapters now have an
107
+ explicit caller-owned operation-ID opt-in. Python accepts the idiomatic
108
+ `operation_id` keyword (or the `operation_id`/`operationId` member of the
109
+ runner input), validates it and all supported combinations before backend
110
+ traffic, then routes exactly one submit followed by at most one collect through
111
+ the shared operation facade. Legacy calls with no operation ID retain their
112
+ existing `runner.run`/`responses.create` transport path. Returned run/response
113
+ data carries the validated operation ID and fresh handle; pending, blocked, and
114
+ uncertain operation envelopes remain partial/blocked results with their
115
+ contract blocker, while identity mismatches fail closed. The full request,
116
+ recovery, coordinator, privacy, and compatibility boundary is documented in
117
+ [Transactional browser operations](2026-08-16-transactional-operations.md).
118
+
119
+ Direct Python `collect`/`run` expose `poll_interval_ms`; transactional Runner
120
+ wait objects additionally accept `pollMs`, `poll_ms`, `pollIntervalMs`, or
121
+ `poll_interval_ms` when aliases agree. The wire field is `pollIntervalMs`, its
122
+ range is the inclusive integer interval `0..60000`, and sleeping never holds a
123
+ browser/tab coordinator transaction.
124
+
33
125
  Incomplete response capture is also shared contract behavior. Python must preserve `status == "partial"`, `output_text`, warnings, and any nested `data.captureLimit` dictionaries exactly as the TypeScript backend returns them. `partial` is not a protocol error: callers should inspect `data.complete` and run another wait/read on the same thread when they need final output.
34
126
 
35
127
  For long-answer polling, Python forwards `response_content="metadata"` to the shared wire field `responseContent: "metadata"` on `messages.wait`. The TypeScript backend then omits assistant text from wait results and returns compact metadata such as `data.responseChars` and `data.responseSha256`; Python must preserve those fields without trying to reconstruct omitted content.
@@ -43,6 +135,23 @@ the same backend commands through `chatgpt.artifacts.list_latest(...)`,
43
135
  `chatgpt.artifacts.wait(...)`, and `chatgpt.artifacts.download_latest(...)`.
44
136
  Those methods forward to `artifacts.listLatest`, `artifacts.wait`, and
45
137
  `artifacts.downloadLatest`; they do not duplicate DOM or selector logic.
138
+
139
+ Python preserves the backend's `download_receipt_timeout` blocker when native
140
+ browser download completion cannot be verified within the deadline. The result
141
+ is blocked, nonresumable and nonrecoverable; the facade does not retry the
142
+ activation or select an alternate download path. Existing browser downloads
143
+ remain in place. Python also preserves `download_blocked_by_browser` when the
144
+ TypeScript runtime identifies Chrome's `ERR_BLOCKED_BY_CLIENT` error page. It
145
+ is a blocked, nonresumable, nonrecoverable command result with no download
146
+ receipt; Python does not retry or duplicate the browser-error detection. The
147
+ shared `download-blocked-by-browser.json` fixture and Python snapshot test lock
148
+ this behavior without adding wire fields or Python-specific browser logic.
149
+ The `download-receipt-failed.json` fixture also preserves the sanitized
150
+ `download_receipt_failed` result from native event or activation failures:
151
+ blocked status, no receipt, and false resumable/recoverable flags. Python keeps
152
+ the fixed backend message and never reconstructs native error details or
153
+ retries an uncertain activation.
154
+
46
155
  If the TypeScript runtime recovers a generated image by reopening a stalled
47
156
  claimed conversation in a temporary bridge-owned tab and exporting through
48
157
  `pageAssets`, Python observes the same command result through the backend
@@ -183,6 +292,30 @@ Python is a native SDK facade over the local backend protocol. The initial brows
183
292
  - Ordinary-shell smoke passes when browser-required calls return structured `browser_bridge_unavailable`.
184
293
  - Browser-bridge runtime smoke remains explicitly gated because it can operate a real ChatGPT session.
185
294
 
295
+ ## Transactional Qualification Is Separate
296
+
297
+ The opt-in Node scenario `transactional-submit-once` uses the default public
298
+ runtime with `CHATGPT_E2E_TRANSACTIONAL=1`. It verifies a fresh-ID multiline,
299
+ zero-file ask, owned collection, a completed receipt, and same-ID replay with
300
+ one Send and one user/assistant exchange. One `ambiguous_submit` recovery is
301
+ permitted only after proving the existing observation-only Send intent and
302
+ matching target; other blockers stop qualification. See [Transactional Live Qualification](backend-protocol.md#transactional-live-qualification)
303
+ for selection and report checks. A host blocker is a failed qualification,
304
+ even when ordinary-shell blocker handling or compatibility browser smokes
305
+ pass.
306
+
307
+ Python's existing `scripts/live_smoke.py --mode browser-bridge` matrix omits
308
+ operation IDs and therefore covers compatibility workflows. Its success does
309
+ not establish transactional parity. This patch adds shared-fixture and
310
+ sync/async `ask` preservation coverage; full Python operation-aware relay
311
+ qualification remains a separate gate. It requires a bridge-hosted Node backend
312
+ with either a supported local journal or the configured journal service, plus
313
+ a permitted Python-to-backend transport. The existing HTTP test relay is
314
+ unavailable where that browser host forbids local sockets; filesystem journal
315
+ transport does not itself provide a Python backend relay. Keep browser
316
+ interaction and ownership verification in TypeScript rather than adding Python
317
+ DOM selectors.
318
+
186
319
  ## Browser-Bridge Smoke
187
320
 
188
321
  Run this only when you intentionally want Python to drive a live backend with browser access:
@@ -246,3 +379,7 @@ Smoke output is a redacted JSON summary. It reports output matches and lengths,
246
379
  | `0` | All browser-bridge scenarios passed. |
247
380
  | `1` | At least one scenario failed unexpectedly. |
248
381
  | `2` | Scenarios recorded documented blockers such as `browser_bridge_unavailable`, `login_required`, or `selector_drift`. |
382
+
383
+ ## Restricted-host journal authority
384
+
385
+ The Node backend can use an explicit asynchronous journal service while keeping browser control in its active host. See [the journal service runbook](2026-09-06-journal-service.md) for startup, recovery, transport boundaries, and the intentional TypeScript host API asymmetry. Operation wire shapes and Python facades are unchanged. Python preserves `journal_rpc_unsupported_platform` when the Node backend rejects the private-file transport on Windows; it does not bypass the platform guard. Existing local Node journal behavior on Windows is unchanged.
@@ -5,6 +5,7 @@
5
5
  Accepted fields:
6
6
 
7
7
  - `input`
8
+ - `operationId` (TypeScript transactional opt-in; caller-owned canonical UUID)
8
9
  - `thread`
9
10
  - `existingTab`
10
11
  - `preferExistingTab`
@@ -22,6 +23,7 @@ Rejected API-only fields return `status: "unsupported"` before any prompt is sub
22
23
 
23
24
  ```ts
24
25
  const response = await chatgpt.responses.create({
26
+ operationId: "123e4567-e89b-42d3-a456-426614174000",
25
27
  input: "Summarize the latest plan.",
26
28
  thread: { type: "conversationId", conversationId: "abc-123" },
27
29
  experience: "chat",
@@ -31,6 +33,23 @@ const response = await chatgpt.responses.create({
31
33
  });
32
34
  ```
33
35
 
36
+ In the TypeScript adapter, supplying `operationId` selects the additive
37
+ transactional runner path and returns the operation ID and fresh handle in
38
+ `response.browser_control` when the mapper reaches the operation boundary.
39
+ Omitting it retains the legacy Responses/runner path. The transactional path
40
+ still accepts only visible-prefix instructions, rejects API-only fields before
41
+ browser use, and does not provide token-delta streaming. With no custom seam,
42
+ the client creates a lazy request-local ChatGPT adapter; it fails closed when
43
+ the bridge, target evidence, or required provider primitive is unavailable.
44
+ Use
45
+ [Transactional browser operations](2026-08-16-transactional-operations.md) for
46
+ submit/collect/inspect/control recovery and capability rules.
47
+
48
+ The Python `ResponsesClient` accepts the idiomatic `operation_id` alias and
49
+ returns the same operation ID and fresh handle in `browser_control`. It validates
50
+ transactional combinations before transport and never resubmits an accepted
51
+ operation.
52
+
34
53
  `experience` and `configuration` represent visible product controls, not API
35
54
  model selection. Configuration is strict through the runner plan and must
36
55
  verify the visible postcondition. Existing callers may continue to pass
@@ -13,3 +13,9 @@ const result = await stream.completed;
13
13
  ```
14
14
 
15
15
  This is not token streaming. Events are emitted for browser-control milestones such as `message_submitted`, `message_completed`, `file_attached`, and `run_blocked`. Do not expect token deltas or OpenAI API stream event parity.
16
+
17
+ Supplying a caller-owned `operationId` opts the runner into the transactional
18
+ submit/collect mapper, but does not change this stream contract. Polling and
19
+ long waits belong to operation collection outside short page transactions; use
20
+ the returned handle with `operations.inspect`/`operations.collect` for exact
21
+ turn recovery. See [Transactional browser operations](2026-08-16-transactional-operations.md).