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

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 (191) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +27 -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/operation-action-prepared-event.json +42 -0
  7. package/contracts/v1/fixtures/operation-action.json +13 -0
  8. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  9. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  10. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  11. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  12. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  13. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  14. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  15. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  16. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  17. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  18. package/contracts/v1/fixtures/operation-event.json +18 -0
  19. package/contracts/v1/fixtures/operation-handle.json +10 -0
  20. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  21. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  22. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  23. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  24. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  25. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  26. package/contracts/v1/fixtures/operation-request.json +33 -0
  27. package/contracts/v1/fixtures/operation-state.json +144 -0
  28. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  29. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  30. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  31. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  32. package/contracts/v1/manifest.json +155 -1
  33. package/contracts/v1/parity-suite.json +96 -1
  34. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  35. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  36. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  37. package/contracts/v1/schemas/manifest.schema.json +57 -0
  38. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  39. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  40. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  41. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  42. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  43. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  44. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  45. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  46. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  47. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  48. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  49. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  50. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  51. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  52. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  53. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  54. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  55. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  56. package/contracts/v1/surface-drift-policy.json +50 -0
  57. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  58. package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
  59. package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
  60. package/dist/src/backend/client.d.ts +103 -2
  61. package/dist/src/backend/client.js +1436 -97
  62. package/dist/src/backend/compatibility.d.ts +12 -0
  63. package/dist/src/backend/compatibility.js +208 -0
  64. package/dist/src/backend/protocol.d.ts +84 -1
  65. package/dist/src/backend/protocol.js +31 -5
  66. package/dist/src/backend/runtime-identity.d.ts +10 -0
  67. package/dist/src/backend/runtime-identity.js +141 -0
  68. package/dist/src/backend/session.d.ts +10 -2
  69. package/dist/src/backend/session.js +478 -7
  70. package/dist/src/backend/stdio-server.d.ts +4 -1
  71. package/dist/src/backend/stdio-server.js +240 -28
  72. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  73. package/dist/src/browser/active-composer-file-input.js +33 -0
  74. package/dist/src/browser/attach.d.ts +4 -1
  75. package/dist/src/browser/attach.js +136 -28
  76. package/dist/src/browser/downloads.d.ts +2 -0
  77. package/dist/src/client.d.ts +38 -0
  78. package/dist/src/client.js +1222 -59
  79. package/dist/src/commands/configuration.js +13 -7
  80. package/dist/src/commands/doctor.d.ts +1 -1
  81. package/dist/src/commands/doctor.js +26 -3
  82. package/dist/src/commands/experience.js +35 -0
  83. package/dist/src/commands/files.js +4 -27
  84. package/dist/src/commands/modes.js +49 -17
  85. package/dist/src/commands/power-discovery.d.ts +130 -0
  86. package/dist/src/commands/power-discovery.js +627 -0
  87. package/dist/src/commands/project-sources.js +325 -31
  88. package/dist/src/commands/sequence.js +59 -32
  89. package/dist/src/commands/session.js +7 -2
  90. package/dist/src/commands/work.js +31 -0
  91. package/dist/src/errors.d.ts +9 -0
  92. package/dist/src/errors.js +22 -0
  93. package/dist/src/index.d.ts +6 -0
  94. package/dist/src/index.js +9 -0
  95. package/dist/src/operations/artifact-output.d.ts +69 -0
  96. package/dist/src/operations/artifact-output.js +1748 -0
  97. package/dist/src/operations/artifact-stream.d.ts +19 -0
  98. package/dist/src/operations/artifact-stream.js +35 -0
  99. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  100. package/dist/src/operations/artifact-transfer.js +1312 -0
  101. package/dist/src/operations/browser-adapter.d.ts +161 -0
  102. package/dist/src/operations/browser-adapter.js +2023 -0
  103. package/dist/src/operations/browser-observation.d.ts +110 -0
  104. package/dist/src/operations/browser-observation.js +1208 -0
  105. package/dist/src/operations/browser-target.d.ts +103 -0
  106. package/dist/src/operations/browser-target.js +489 -0
  107. package/dist/src/operations/canonical.d.ts +4 -0
  108. package/dist/src/operations/canonical.js +412 -0
  109. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  110. package/dist/src/operations/chatgpt-runtime.js +1150 -0
  111. package/dist/src/operations/client.d.ts +138 -0
  112. package/dist/src/operations/client.js +1143 -0
  113. package/dist/src/operations/collector.d.ts +195 -0
  114. package/dist/src/operations/collector.js +1039 -0
  115. package/dist/src/operations/control.d.ts +372 -0
  116. package/dist/src/operations/control.js +1498 -0
  117. package/dist/src/operations/file-identity.d.ts +33 -0
  118. package/dist/src/operations/file-identity.js +153 -0
  119. package/dist/src/operations/handle.d.ts +19 -0
  120. package/dist/src/operations/handle.js +727 -0
  121. package/dist/src/operations/index.d.ts +22 -0
  122. package/dist/src/operations/index.js +22 -0
  123. package/dist/src/operations/journal.d.ts +142 -0
  124. package/dist/src/operations/journal.js +1931 -0
  125. package/dist/src/operations/production-attachments.d.ts +116 -0
  126. package/dist/src/operations/production-attachments.js +1018 -0
  127. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  128. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  129. package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
  130. package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
  131. package/dist/src/operations/production-configuration.d.ts +42 -0
  132. package/dist/src/operations/production-configuration.js +1363 -0
  133. package/dist/src/operations/production-primitives.d.ts +53 -0
  134. package/dist/src/operations/production-primitives.js +1073 -0
  135. package/dist/src/operations/production-work-steer.d.ts +203 -0
  136. package/dist/src/operations/production-work-steer.js +1218 -0
  137. package/dist/src/operations/recovery.d.ts +80 -0
  138. package/dist/src/operations/recovery.js +174 -0
  139. package/dist/src/operations/runtime-adapter.d.ts +118 -0
  140. package/dist/src/operations/runtime-adapter.js +786 -0
  141. package/dist/src/operations/send-once.d.ts +224 -0
  142. package/dist/src/operations/send-once.js +1081 -0
  143. package/dist/src/operations/service.d.ts +281 -0
  144. package/dist/src/operations/service.js +2810 -0
  145. package/dist/src/operations/staging.d.ts +136 -0
  146. package/dist/src/operations/staging.js +630 -0
  147. package/dist/src/operations/state-machine.d.ts +29 -0
  148. package/dist/src/operations/state-machine.js +2047 -0
  149. package/dist/src/operations/submission.d.ts +423 -0
  150. package/dist/src/operations/submission.js +1676 -0
  151. package/dist/src/operations/turn-ownership.d.ts +202 -0
  152. package/dist/src/operations/turn-ownership.js +700 -0
  153. package/dist/src/operations/types.d.ts +454 -0
  154. package/dist/src/operations/types.js +20 -0
  155. package/dist/src/operations/wire-requests.d.ts +21 -0
  156. package/dist/src/operations/wire-requests.js +396 -0
  157. package/dist/src/operations/wire-results.d.ts +97 -0
  158. package/dist/src/operations/wire-results.js +818 -0
  159. package/dist/src/runner/responses.d.ts +3 -1
  160. package/dist/src/runner/responses.js +19 -1
  161. package/dist/src/runner/result.js +227 -55
  162. package/dist/src/runner/types.d.ts +12 -0
  163. package/dist/src/runtime/command-routing.d.ts +149 -0
  164. package/dist/src/runtime/command-routing.js +431 -0
  165. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  166. package/dist/src/runtime/coordinated-browser.js +315 -0
  167. package/dist/src/runtime/coordinated-page.d.ts +39 -0
  168. package/dist/src/runtime/coordinated-page.js +634 -0
  169. package/dist/src/runtime/operation-context.d.ts +142 -0
  170. package/dist/src/runtime/operation-context.js +410 -0
  171. package/dist/src/runtime/runtime-session.d.ts +95 -0
  172. package/dist/src/runtime/runtime-session.js +314 -0
  173. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  174. package/dist/src/runtime/tab-coordinator.js +1200 -0
  175. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  176. package/dist/src/runtime/value-boundaries.js +49 -0
  177. package/dist/src/safety/untrusted-output.js +2 -1
  178. package/dist/src/scripts/backend-server.js +4 -1
  179. package/dist/src/scripts/live-smoke/harness.js +79 -7
  180. package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
  181. package/dist/src/scripts/live-smoke/scenarios.js +110 -18
  182. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  183. package/dist/src/scripts/release-canary-module.js +17 -4
  184. package/dist/src/types.d.ts +38 -1
  185. package/package.json +1 -1
  186. package/references/2026-08-16-transactional-operations.md +459 -0
  187. package/references/agents-runner.md +34 -0
  188. package/references/backend-protocol.md +73 -0
  189. package/references/python-parity.md +72 -0
  190. package/references/responses-adapter.md +19 -0
  191. package/references/streaming.md +6 -0
@@ -0,0 +1,459 @@
1
+ ---
2
+ title: Transactional browser operations
3
+ date: 2026-08-16
4
+ type: reference
5
+ status: preview
6
+ ---
7
+
8
+ # Transactional browser operations (v1)
9
+
10
+ This is the canonical reference for the additive transactional operation
11
+ surface. It describes the versioned request/result contract, the TypeScript
12
+ and Python facades, and the boundary between the new operation path and the
13
+ existing browser-control primitives.
14
+
15
+ ## Status and current boundary
16
+
17
+ The v1 journal, strict wire validators, recovery state machine, browser-adapter
18
+ seams, Node backend commands, and direct Python sync/async clients are present
19
+ in the source tree. This is a preview contract, not a claim that every
20
+ provider/browser primitive is live.
21
+
22
+ In the current source, `createChatGPT()` opens the operation journal lazily.
23
+ `chatgpt.operations.inspect(handle)` is deliberately browser-free and can
24
+ inspect an authenticated local record. When no custom operation seam is
25
+ supplied, browser-touching `submit`, non-terminal `collect`, and `control` use
26
+ lazy request-local ChatGPT factories. Browser attachment happens only after
27
+ the journal has accepted the operation; recovery attaches by the authenticated
28
+ durable tab ID and never falls back to the selected tab. Missing bridge access,
29
+ target evidence, or a required provider primitive fails closed instead of
30
+ routing through the legacy command sequence. Supplying any custom adapter seam
31
+ is an all-or-nothing integration choice so a custom provider is never combined
32
+ with part of the default recovery path. The current ordinary backend capability
33
+ response still advertises no stable provider/browser/tab identity,
34
+ authoritative tab claim, fencing, or concurrent-tab guarantee.
35
+
36
+ Therefore:
37
+
38
+ - Contract, validation, journal, and browser-free inspection claims are
39
+ deterministic source/test claims.
40
+ - A live submit/collect/control claim requires a bridge-enabled default runtime
41
+ or a complete custom adapter, an authorized page, target evidence, and the
42
+ provider primitives needed by that operation, followed by the relevant live
43
+ smoke.
44
+ - A browser bridge blocker, unavailable primitive, missing target evidence, or
45
+ runtime mismatch is a structured boundary result; it is not evidence that
46
+ ChatGPT itself is unavailable.
47
+
48
+ The authenticated journal records a complete redacted ownership baseline
49
+ before each non-repeatable Send or Work-steer intent. New records retain these
50
+ in `ownershipBaselines[actionId]`; `ownershipBaseline` remains the compatibility
51
+ projection of the original Send. Only after the exact post-action user-turn
52
+ delta has been observed does the journal append the immutable keyed witness in
53
+ `submissionWitnesses[actionId]`; `submissionWitness` likewise remains the
54
+ original-Send compatibility projection. Each witness contains action/target
55
+ identities and digests, never prompt text, DOM, response text, URLs, or local
56
+ paths. A satisfied Work steer requires its exact baseline+witness pair, a clean
57
+ non-mutating `not_satisfied` steer retains its baseline but has no witness, and
58
+ an unresolved/uncertain steer blocks a later steer. Collection authenticates
59
+ and classifies against the latest satisfied causal action rather than silently
60
+ falling back to the original Send. A durable witness carries
61
+ `baselineSnapshotDigest`, the digest of its complete pre-action ownership
62
+ snapshot, alongside the post-action delta digest. The state reducer, service,
63
+ collector, and Python model require that value to equal the keyed durable
64
+ baseline; a witness cannot be replayed against a different action or baseline.
65
+ A legacy target-establishment
66
+ projection may be read without `postSendDeltaDigest`, but that record cannot
67
+ establish a new target or prove submitted ownership; every newly emitted
68
+ submission witness and target-establishment event must carry its required
69
+ causal evidence.
70
+
71
+ There is intentionally no permissive migration from a proof-less experimental
72
+ snapshot into `submitted`, `generating`, `capturing`, or `completed`. Those
73
+ phases require both keyed records for the original Send. A snapshot that only
74
+ has the compatibility projections fails closed and must be inspected or
75
+ explicitly discarded by its owner; the runtime cannot reconstruct a pre-Send
76
+ baseline or post-Send delta after the fact.
77
+
78
+ ## Identity model
79
+
80
+ There are several intentionally different identities:
81
+
82
+ | Identity | Owner and purpose | Safe to persist/echo? |
83
+ | --- | --- | --- |
84
+ | `operationId` | Caller-owned canonical UUID. It names one immutable logical operation and opts high-level helpers into this path. | Yes, as an opaque identity. Never generate a replacement when recovering. |
85
+ | `requestDigest` | Journal-keyed digest of the canonical request plus file manifest. It detects a changed retry. | Yes, as a digest; not as a substitute for the request. |
86
+ | `handle` | Fresh locator containing operation identity, digest, surface, revision, phase, mutation boundary, and optional target-binding digest. | Yes, as a versioned locator. It is not an authority token. |
87
+ | transport `requestId` | Backend connection identity used to demultiplex one NDJSON call. | Diagnostic only; it is not operation identity. |
88
+ | `backendSessionId` | Runtime/backend owner identity used by coordination and capability diagnostics. | Diagnostic only. |
89
+ | target binding | Authenticated provider/browser/tab/thread evidence bound to the journal record. | Persist only in its redacted/digest form defined by the contract. |
90
+
91
+ The caller owns `operationId` and must reuse it for a retry or recovery. A
92
+ different prompt, target, configuration, or file manifest under the same ID
93
+ does not create a second operation: the journal returns an identity mismatch.
94
+ The journal and action-intent CAS rules make a repeated same-ID submit
95
+ observation/recovery path unable to activate Send twice.
96
+
97
+ The TypeScript journal remains the runtime authority for request-digest
98
+ computation. Its digest primitive is
99
+ `HMAC-SHA-256(key, UTF8(domain + "\0" + canonicalJson(value)))`, returned with
100
+ the `hmac-sha256:` prefix. Every operation and journal domain uses the stable
101
+ `codex-chatgpt-control/.../v1` namespace in both private-source and exported
102
+ public builds; package-name rewriting must never alter persisted identity.
103
+ Canonical JSON sorts object keys lexicographically,
104
+ retains array order, normalizes negative zero, uses explicit internal markers
105
+ for `undefined`, dates, and bytes, and rejects caller objects containing those
106
+ reserved marker keys. The request projection binds schema/operation/surface,
107
+ target, keyed prompt digest plus UTF-8 length, configuration/tools, ordered
108
+ keyed file-name/content digests plus byte counts, path-free capture policy, and
109
+ behavior. `capturePolicy.outputDirectory` is intentionally excluded because a
110
+ separate transfer action binds destination authority. The privacy-safe fixed-key
111
+ golden vector at
112
+ `contracts/v1/vectors/operation-request-digest-v1.json` freezes the canonical
113
+ bytes, nested domains, and final digest. Node computes it in production; Node
114
+ and a test-only Python reference both verify the same vector so the Python
115
+ facade does not become a second runtime authority.
116
+
117
+ ## Canonical direct surface
118
+
119
+ The backend wire commands are exactly:
120
+
121
+ - `operations.submit`
122
+ - `operations.collect`
123
+ - `operations.inspect`
124
+ - `operations.control`
125
+
126
+ `operations.run` is an SDK-only composition of one `submit` followed by at
127
+ most one `collect`; it is not a fifth backend command and must not be treated
128
+ as an independently idempotent provider primitive.
129
+
130
+ ### Submit request
131
+
132
+ The request is strict JSON. Optional properties are omitted rather than sent
133
+ as `null`.
134
+
135
+ ```ts
136
+ const request = {
137
+ schemaVersion: "chatgpt.browser_control.operation_request.v1",
138
+ operationId: "123e4567-e89b-42d3-a456-426614174000", // caller-owned UUID
139
+ surface: "chat",
140
+ prompt: "Summarize the visible thread.",
141
+ target: {
142
+ type: "conversation_id",
143
+ conversationId: "conversation-id-from-the-caller"
144
+ },
145
+ configuration: {
146
+ experience: "chat",
147
+ modelVersion: "visible-version-label",
148
+ reasoning: "high",
149
+ tools: ["web_search"]
150
+ },
151
+ capture: {
152
+ responseContent: "metadata",
153
+ responseFormat: "markdown",
154
+ artifacts: "receipt_only"
155
+ }
156
+ };
157
+
158
+ // The default client captures the visible ChatGPT runtime lazily after the
159
+ // journal accepts the operation. A custom adapter configuration must provide
160
+ // the complete submit/recovery/control set; missing proof still fails closed.
161
+ const submitted = await chatgpt.operations.submit(request);
162
+ ```
163
+
164
+ `surface` is `chat` or `work`. The target is one of `{ type: "new" }`,
165
+ `{ type: "selected_tab" }`, `{ type: "tab_id", tabId }`,
166
+ `{ type: "conversation_id", conversationId }`, or
167
+ `{ type: "url", url }`. `files` contains request-only absolute host-local
168
+ paths; the operation client fingerprints and revalidates them before a file
169
+ handoff. `capture.responseContent` is `include` or `metadata`; artifact
170
+ capture is `receipt_only` or `transfer`, with an explicit request-only
171
+ `outputDirectory` required for transfer.
172
+
173
+ `capture.responseFormat` is an immutable request-identity field and accepts
174
+ only `markdown` (the default) or `text`. `markdown` is reconstructed from
175
+ semantic DOM structure for the exact owned assistant turn; `text` is the
176
+ formatter's normalized visible text. The bounded transient `innerHTML` used
177
+ for that one exact turn is formatted immediately and is never written to the
178
+ journal, durable receipt, snapshot, or report. Collection fails closed when
179
+ the requested format, exact assistant ID, provider shape, or content bounds
180
+ cannot be proven.
181
+
182
+ Once a request is created, its capture contract is copied into the
183
+ path-free `capturePolicy` field on the `operation_created` event and materialized
184
+ state. The durable shape is exactly `{ responseContent, responseFormat,
185
+ artifacts }`; `responseFormat` defaults to `markdown`, omitted capture defaults
186
+ to `include` plus `receipt_only`, and request-local `outputDirectory` is never
187
+ persisted. A restart therefore retains an `artifacts: "transfer"` obligation
188
+ even when the original destination is unavailable; recovery must not silently
189
+ rewrite it to `receipt_only`.
190
+
191
+ In the TypeScript in-process facade, `submit` returns a fresh `handle` plus a
192
+ service-native submission result. `submission.kind` is one of the explicit
193
+ submitted/already-submitted/completed-receipt/blocked/uncertain/cancelled
194
+ outcomes. The backend and Python facades convert this to the strict
195
+ `operation_submit_result.v1` envelope:
196
+
197
+ - `accepted`: submission was reconciled or was already durably submitted; it
198
+ is not completion.
199
+ - `completed`: a durable receipt is already available.
200
+ - `blocked`: the mutation boundary is known not to have crossed.
201
+ - `uncertain`: a handoff or Send may have crossed a non-repeatable boundary;
202
+ observe/recover, do not resubmit.
203
+
204
+ ### Collect and inspect
205
+
206
+ Always carry forward the freshest returned handle. `inspect` reads the
207
+ authenticated journal and does not use a browser. `collect` addresses only the
208
+ operation-owned assistant turn; it never falls back to “latest response” or
209
+ submits a prompt. A backend/Python flow is shaped like this:
210
+
211
+ ```python
212
+ from codex_chatgpt_control import ChatGPT
213
+
214
+ # backend is an already-configured BackendClient or compatible test double.
215
+ chatgpt = ChatGPT(backend=backend)
216
+ submitted = chatgpt.operations.submit(
217
+ operation_id="123e4567-e89b-42d3-a456-426614174000",
218
+ surface="chat",
219
+ prompt="Summarize the visible thread.",
220
+ target={"type": "conversation_id", "conversationId": "conversation-id-from-the-caller"},
221
+ capture={"responseContent": "metadata", "responseFormat": "markdown", "artifacts": "receipt_only"},
222
+ )
223
+
224
+ handle = submitted.handle
225
+ if submitted.status == "accepted":
226
+ inspected = chatgpt.operations.inspect(handle=handle)
227
+ collected = chatgpt.operations.collect(
228
+ handle=inspected.handle,
229
+ wait=False,
230
+ response_content="metadata",
231
+ )
232
+ print(collected.status) # completed, pending, blocked, or uncertain
233
+ ```
234
+
235
+ The versioned wire envelopes carry `operationId`, `requestDigest`, and a fresh
236
+ handle on every result. Completed results carry a durable receipt. Blocked or
237
+ uncertain results carry a redacted blocker with phase and mutation boundary.
238
+ An included raw response is a separately marked ephemeral `liveResponse`; it
239
+ is not part of durable state or a receipt.
240
+
241
+ `inspect` reports `completed`, `pending`, or `uncertain` from the durable phase.
242
+ `collect` reports `completed`, `pending`, `blocked`, or `uncertain`.
243
+ `control` reports `completed`, `blocked`, or `uncertain` and is covered below.
244
+
245
+ ### Control
246
+
247
+ `control` is operation-bound and requires a fresh generating parent handle, an
248
+ exact assistant turn ID, and a caller-owned `controlActionId`. Its action is
249
+ `stop` or `steer`; it does not accept a generic “latest” target. Control has
250
+ its own action digest but returns the parent operation identity and a fresh
251
+ parent handle. A failed or uncertain control result is never an instruction
252
+ to repeat Stop/steer automatically.
253
+
254
+ ## Recovery and retry rules
255
+
256
+ The safe recovery loop is:
257
+
258
+ 1. Persist the latest redacted handle and the caller-owned `operationId` in the
259
+ caller's own recovery record.
260
+ 2. Call `inspect` after a timeout, process restart, or lost response.
261
+ 3. If the operation is not terminal, call `collect` with that handle to observe
262
+ the owned turn. Use `responseContent: "metadata"` while polling when raw
263
+ text is not needed.
264
+ 4. For an uncertain handoff or Send, inspect the visible target/postcondition
265
+ and reconcile the durable action intent. Do not call `submit` again with a
266
+ new ID or a changed request.
267
+ 5. Use `control` only with the exact current generating handle and assistant
268
+ turn ID, after an explicit caller decision.
269
+
270
+ Handles are locators, not bearer authority. After a restart, a configured
271
+ `handleAdapterFactory` must rebuild a target-bound adapter only after the
272
+ journal authenticates the handle. If stable target evidence, page affinity,
273
+ or an authoritative provider claim cannot be proven, the result must remain a
274
+ blocker/uncertain outcome. A handle does not authorize taking over another
275
+ tab, conversation, or user's turn.
276
+
277
+ ## High-level opt-in behavior
278
+
279
+ The legacy path remains the compatibility boundary. TypeScript high-level
280
+ helpers select the transactional mapper only when a caller supplies a
281
+ canonical `operationId`; with no ID they retain the existing workflow/runner
282
+ sequence. Unsupported transactional fields are rejected before browser use,
283
+ and the result carries the supplied operation ID in success, blocker, partial,
284
+ or structured error data.
285
+
286
+ | Surface | With caller-owned `operationId` | Without it / current limitation |
287
+ | --- | --- | --- |
288
+ | `chatgpt.ask`, `askInThread`, `askWithFiles` | One transactional submit/collect composition. Target must be explicit enough for the mapper; response capture is metadata or Markdown/text. | Existing workflow sequence, unchanged compatibility behavior. |
289
+ | `askAndDownload` | Download is rejected before browser use; collect the operation and use an explicit artifact-transfer operation when that adapter is available. | Existing download workflow remains available. |
290
+ | `runner.run` | Same transactional mapper as Chat. Agent instructions use the rendered visible prompt semantics; `visible_setup_message` and `copy` are rejected because they require additional/legacy turns. Stream events remain milestones only. | Existing runner plan/sequence behavior. |
291
+ | `responses.create` (TypeScript) | `operationId` opts into the same runner mapper. `instructions` are visible-prefix text only; API-only fields remain unsupported; operation ID and handle are returned in `browser_control`. | Existing narrow Responses-shaped runner adapter. |
292
+ | `work.start` (TypeScript) | Uses `surface: "work"`, returns operation ID/handle and exact owned-turn state. Wait/read is collect-only; unsupported configuration/wait fields fail before browser use. | Existing Work command path. |
293
+ | `work.steer` (TypeScript) | Requires the generating Work handle, matching operation ID, control action ID, and exact assistant turn ID; the parent operation is never resubmitted. | Existing Work steer primitive. |
294
+ | Python `chatgpt.operations` | Direct sync/async v1 envelopes and field aliases are parity facades over the shared Node backend. Python Runner/Responses also use this facade when given `operation_id`. | Calls without an operation ID retain the legacy Python runner/Responses paths. |
295
+
296
+ The current transactional mapper intentionally does not claim support for
297
+ `visible_setup_message`, copy/download/report side effects, arbitrary API model
298
+ controls, title/history search targets, or wait semantics that are not tied to
299
+ the owned turn. Rejections are input results, not browser attempts.
300
+
301
+ ## Work, Runner, and Responses semantics
302
+
303
+ Rendered agent instructions remain visible prompt content. The transactional
304
+ runner does not turn them into hidden system messages and does not change the
305
+ legacy rendering/precedence rules for target, configuration, wait, or read
306
+ values. It does not promise token deltas: all runner streaming is milestone
307
+ streaming (`message.in_progress`, `message.completed`, and related setup or
308
+ blocker milestones).
309
+
310
+ Work start is a single operation on the Work surface. A `read` request means
311
+ “collect this operation-owned turn”, not “read whichever response is latest”.
312
+ Work steer is a separate operation-bound control action. It cannot combine
313
+ steer with a second wait/read request, and it must not be used to submit the
314
+ original Work prompt again.
315
+
316
+ The TypeScript and Python Responses adapters accept a caller-owned operation ID
317
+ (`operationId` / `operation_id`) and route it through the same one-submit,
318
+ at-most-one-collect mapper. Both preserve operation identity and the fresh
319
+ handle in their browser-control metadata; malformed or API-only combinations
320
+ are rejected before backend traffic, and an accepted operation is never
321
+ resubmitted internally.
322
+
323
+ Direct collect calls and high-level transactional waits may set a bounded
324
+ `pollIntervalMs` (`poll_interval_ms` in Python; Runner wait objects also accept
325
+ the documented poll aliases). It affects only the sleep between observation
326
+ attempts, accepts integers from `0` through `60000`, and does not hold the
327
+ browser/tab coordinator while sleeping.
328
+
329
+ ## Per-tab and shared-browser coordination
330
+
331
+ The coordinator is deliberately short-lived at the browser boundary:
332
+
333
+ - One target binding captures one explicit page and stable provider/browser/tab
334
+ evidence. Every read or mutation re-checks page/target affinity before acting.
335
+ - A short page transaction acquires a process-local tab actor when stable
336
+ provider/browser/tab identities and concurrent-tab capability are proven.
337
+ An authoritative claim/fencing token additionally upgrades that binding to
338
+ provider coordination scope; it is not fabricated as a prerequisite for
339
+ safe coordination among cooperating callers in this one process.
340
+ - If those capabilities are absent or downgraded, the resource scope is the
341
+ process-local browser actor. The implementation must not infer safe
342
+ per-tab concurrency from an unverified tab label.
343
+ - Hashing/revalidation, journal I/O, report work, polling, and sleeps occur
344
+ outside the coordinator actor. A long ChatGPT generation therefore does not
345
+ hold the tab/browser scheduler lock.
346
+ - A deadline or cancellation quarantines an in-flight callback until it
347
+ settles; callers must not start a duplicate non-repeatable action merely
348
+ because the host request returned.
349
+ - The process-local coordinator only coordinates cooperating callers in one
350
+ process. It is not a provider-level or cross-process fencing guarantee.
351
+
352
+ The public package exports `createProcessTabCoordinator`/`ProcessTabCoordinator`
353
+ and `createOperationRuntimeContext`/`OperationRuntimeContext` because the
354
+ public browser-adapter options accept injected coordinator and runtime-context
355
+ instances. Integrations should inject these only with provider evidence that
356
+ matches the negotiated identity and concurrency capabilities.
357
+
358
+ The current default backend advertises `tabs.coordinationScope: "none"` and
359
+ `tabs.concurrentTabs: false`. A future/provider-backed integration may
360
+ advertise stronger capabilities, but callers must check the negotiated
361
+ `backend.hello`/`backend.capabilities` result and retain the conservative
362
+ scope when identity or fencing is unavailable. Concurrent operations against a
363
+ shared browser are not categorically forbidden; they are allowed only where
364
+ the provider capability and target-binding evidence prove that the distinct
365
+ tab actors are safe.
366
+
367
+ ## Version and capability checks
368
+
369
+ Before relying on an operation or concurrency guarantee, negotiate the backend
370
+ identity/capabilities. Check at least:
371
+
372
+ - `protocolVersion` and `supportedProtocolVersions`;
373
+ - the four `operations.*` command names;
374
+ - package/runtime/build identity (`packageVersion`, `runtimeVersion`,
375
+ `buildDigest`), retaining `unknown` as unknown rather than guessing;
376
+ - request IDs, multiplexing, and cancellation claims; and
377
+ - `tabs` identity, coordination scope, authoritative claim/fencing, and
378
+ `concurrentTabs`.
379
+
380
+ A package/runtime/build value that is old or unknown is a warning for
381
+ diagnostics and deployment policy. It is not proof of incompatibility. A
382
+ proven protocol mismatch, unsupported required command, failed capability
383
+ intersection, or an adapter that cannot satisfy target/page affinity is a
384
+ blocker (`runtime_incompatible`, `backend_unavailable`, or the more specific
385
+ target blocker). Do not turn a version warning into a live-support claim.
386
+
387
+ ## Bounded transport and artifact I/O
388
+
389
+ Multiplexing does not mean unbounded buffering. Node and Python persistent
390
+ clients bound each stream by event count and encoded UTF-8 bytes, isolate an
391
+ overflow to that correlated route, and retain bounded tombstones for expected
392
+ late terminal records. The Node client additionally bounds aggregate queued
393
+ stdin frames by count and bytes. A queued request is re-authorized immediately
394
+ before its frame is written; cancellation before that point prevents the late
395
+ write. A started stdin write that never settles forces process teardown, and a
396
+ second unresolved write generation fails closed rather than accumulating
397
+ unbounded promise chains.
398
+
399
+ Operation artifact transfer is streaming and holds only a bounded provider
400
+ chunk plus filesystem buffers. Provider `open`, `next`, and `return` calls have
401
+ hard request/deadline boundaries and a 30-second internal fallback when the
402
+ caller supplies no shorter bound. Late settlements are observed but never
403
+ retried. One provider chunk is limited to 8 MiB and one artifact to 65,536
404
+ chunks; the built-in 64 KiB adapters need at most 8,192 chunks for the 512 MiB
405
+ artifact ceiling. This admits an immediately terminal empty artifact while an
406
+ infinite sequence of empty/tiny chunks fails closed. Local output uses a
407
+ restrictive operation-owned temporary file, hashes while writing, syncs before
408
+ exclusive installation, and never overwrites an unrelated destination.
409
+
410
+ The transactional artifact provider has a stricter capability boundary than
411
+ the legacy download helpers. Browser-backed enumeration and acquisition require
412
+ a bounded `page.evaluate` implementation plus a causal download-event waiter.
413
+ For bytes, `DownloadLike.createReadStream()` is preferred. A completed local
414
+ `path()` is accepted only when it names an absolute regular file that can be
415
+ opened with `O_NOFOLLOW`, retained by file descriptor, and proven to keep the
416
+ same file identity and bounded size. A provider exposing only `saveAs(path)` is
417
+ rejected without calling it: pure Node cannot prevent that pathname capability
418
+ from being redirected after validation. Custom adapters must implement these
419
+ operation capabilities explicitly; support for legacy `saveAs()` elsewhere in
420
+ the SDK does not imply transactional artifact-transfer support.
421
+
422
+ ## Privacy, retention, and redaction
423
+
424
+ The operation boundary is designed so durable state contains identities,
425
+ digests, counts, phases, action intents/receipts, redacted blockers, target
426
+ evidence, and artifact metadata—not raw prompts, local input paths, or raw
427
+ assistant text. Request-only paths are replaced by bounded placeholders before
428
+ the service persists a request. File contents are fingerprinted locally and
429
+ revalidated before handoff; hashes are diagnostics, not content transfer.
430
+
431
+ The local journal is authenticated and append-only in normal operation, uses a
432
+ state-root key and restricted local file permissions, and defaults to a 64 MiB
433
+ state quota. Quota admission also has fixed fail-closed scan ceilings of 65,536
434
+ directory entries and 256 MiB of observed file data. Exceeding the configured
435
+ state quota returns `journal_quota_exceeded`; exceeding either hard scan ceiling
436
+ returns `journal_scan_limit`. Neither condition deletes unresolved or terminal
437
+ deduplication evidence automatically. Completed receipts may be compacted;
438
+ explicit pruning leaves an
439
+ authenticated tombstone so expiry is distinguishable from “not found”. These
440
+ are local retention mechanisms, not a promise of indefinite history. Raw
441
+ response text, when requested, is an explicitly ephemeral value and is never a
442
+ durable receipt. Keep live reports, prompts, responses, paths, account data,
443
+ and credentials out of public fixtures and durable docs.
444
+
445
+ ## Compatibility boundary
446
+
447
+ The existing `ask`, `runner`, Work, Responses, message, attachment, download,
448
+ and report primitives are still supported where documented. The transactional
449
+ surface is additive and does not silently rewrite callers that omit
450
+ `operationId`. Legacy primitives do not acquire operation journaling,
451
+ submit-once recovery, operation handles, or cross-process concurrency merely
452
+ because the same client also exposes `chatgpt.operations`.
453
+
454
+ Use the direct v1 surface when the caller needs durable identity, exact target
455
+ ownership, inspect/collect recovery, or an auditable mutation boundary. Use
456
+ the legacy surface when its broader visible workflow semantics are required
457
+ and the caller accepts the legacy retry/timeout boundary. Do not mix the two
458
+ paths for one logical Send unless the caller has an explicit, reviewed bridge
459
+ between their identities.
@@ -30,6 +30,40 @@ const result = await chatgpt.runner.run(reviewer, {
30
30
 
31
31
  `runner.run()` returns a `ChatGPTRunResult` with `output_text`, `finalOutput`, `output`, `interruptions`, and `state`. Browser-control blockers are surfaced as resumable interruptions when the underlying command can be retried after user approval, login, or permission repair.
32
32
 
33
+ ### Transactional runner opt-in
34
+
35
+ The runner remains on its existing workflow/sequence path unless the caller
36
+ supplies a canonical, caller-owned `operationId`:
37
+
38
+ ```ts
39
+ const result = await chatgpt.runner.run(reviewer, {
40
+ operationId: "123e4567-e89b-42d3-a456-426614174000",
41
+ input: "Review this design.",
42
+ thread: { type: "conversationId", conversationId: "conversation-id-from-the-caller" },
43
+ response: { format: "markdown" }
44
+ });
45
+
46
+ // Transactional results carry the same operation identity and locator in data.
47
+ console.log(result.data?.operationId, result.data?.handle);
48
+ ```
49
+
50
+ With an operation ID, the runner reuses the transactional Chat mapper: the
51
+ rendered agent instructions remain visible prompt text, target/configuration
52
+ precedence is preserved, and the caller receives operation identity in success,
53
+ partial, blocker, and structured error data. The transactional path rejects
54
+ `visible_setup_message` (it would require a separate setup turn) and `copy`
55
+ before browser use. Downloads/reports and unsupported wait/read fields remain
56
+ explicitly rejected rather than being silently emulated. Omit the ID to retain
57
+ legacy behavior byte-for-behavior.
58
+
59
+ With no custom operation seam, `createChatGPT()` installs lazy request-local
60
+ ChatGPT adapter factories. They still fail closed when the bridge, target
61
+ evidence, or required provider primitive is unavailable, and their presence is
62
+ not a live-capability claim. For durable recovery, persist the returned handle
63
+ and use inspect/collect on the same operation; never retry the runner with a new
64
+ ID after an uncertain Send. See [Transactional browser operations](2026-08-16-transactional-operations.md)
65
+ for the direct v1 surface and capability boundary.
66
+
33
67
  `experience` and `configuration` are visible product preferences. When present,
34
68
  the plan emits `experience.open` and strict `configuration.apply` steps before
35
69
  the prompt. Successful results expose `experience.opened` and
@@ -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,42 @@ 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
+ `operations.collect` optionally accepts `pollIntervalMs`, an integer from `0`
203
+ through `60000`. It controls only the interval between bounded observation
204
+ attempts. Poll sleeps occur outside browser/tab transactions.
132
205
 
133
206
  ## Host-Local Attachment Paths
134
207
 
@@ -30,6 +30,78 @@ 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
+ The TypeScript and Python high-level Runner and Responses adapters now have an
87
+ explicit caller-owned operation-ID opt-in. Python accepts the idiomatic
88
+ `operation_id` keyword (or the `operation_id`/`operationId` member of the
89
+ runner input), validates it and all supported combinations before backend
90
+ traffic, then routes exactly one submit followed by at most one collect through
91
+ the shared operation facade. Legacy calls with no operation ID retain their
92
+ existing `runner.run`/`responses.create` transport path. Returned run/response
93
+ data carries the validated operation ID and fresh handle; pending, blocked, and
94
+ uncertain operation envelopes remain partial/blocked results with their
95
+ contract blocker, while identity mismatches fail closed. The full request,
96
+ recovery, coordinator, privacy, and compatibility boundary is documented in
97
+ [Transactional browser operations](2026-08-16-transactional-operations.md).
98
+
99
+ Direct Python `collect`/`run` expose `poll_interval_ms`; transactional Runner
100
+ wait objects additionally accept `pollMs`, `poll_ms`, `pollIntervalMs`, or
101
+ `poll_interval_ms` when aliases agree. The wire field is `pollIntervalMs`, its
102
+ range is the inclusive integer interval `0..60000`, and sleeping never holds a
103
+ browser/tab coordinator transaction.
104
+
33
105
  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
106
 
35
107
  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.