codex-chatgpt-control 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -0
  4. package/contracts/v1/fixtures/backend-capabilities.json +54 -0
  5. package/contracts/v1/fixtures/backend-error-event-missing-stream-input.json +10 -0
  6. package/contracts/v1/fixtures/backend-error-missing-run-input.json +10 -0
  7. package/contracts/v1/fixtures/backend-runner-plan-request.json +12 -0
  8. package/contracts/v1/fixtures/backend-version.json +10 -0
  9. package/contracts/v1/fixtures/command-descriptors.json +803 -0
  10. package/contracts/v1/fixtures/describe-runner-run.json +31 -0
  11. package/contracts/v1/fixtures/doctor-bridge-upload.json +34 -0
  12. package/contracts/v1/fixtures/help-root.json +6 -0
  13. package/contracts/v1/fixtures/named-plan-two-turn.json +39 -0
  14. package/contracts/v1/fixtures/output-json-parse-success.json +115 -0
  15. package/contracts/v1/fixtures/primitive-bootstrap-blocker.json +43 -0
  16. package/contracts/v1/fixtures/report-redaction-default.json +16 -0
  17. package/contracts/v1/fixtures/reports-create-redacted.json +16 -0
  18. package/contracts/v1/fixtures/reports-summarize-redacted.json +26 -0
  19. package/contracts/v1/fixtures/responses-basic-success.json +27 -0
  20. package/contracts/v1/fixtures/responses-hidden-instructions-unsupported.json +22 -0
  21. package/contracts/v1/fixtures/responses-unknown-field-unsupported.json +22 -0
  22. package/contracts/v1/fixtures/responses-unsupported-previous-response-id.json +22 -0
  23. package/contracts/v1/fixtures/responses-unsupported-temperature.json +22 -0
  24. package/contracts/v1/fixtures/run-basic-success.json +36 -0
  25. package/contracts/v1/fixtures/run-browser-bridge-blocker.json +180 -0
  26. package/contracts/v1/fixtures/run-file-attach-success.json +63 -0
  27. package/contracts/v1/fixtures/run-report-redacted.json +52 -0
  28. package/contracts/v1/fixtures/run-selector-drift-blocker.json +60 -0
  29. package/contracts/v1/fixtures/run-timeout-partial.json +81 -0
  30. package/contracts/v1/fixtures/run-two-turn-success.json +83 -0
  31. package/contracts/v1/fixtures/run-upload-permission-blocker.json +56 -0
  32. package/contracts/v1/fixtures/runner-budget-blocker.json +106 -0
  33. package/contracts/v1/fixtures/runner-full-agent-config.json +50 -0
  34. package/contracts/v1/fixtures/runner-input-items-and-files-plan.json +53 -0
  35. package/contracts/v1/fixtures/runner-metadata-only-plan.json +31 -0
  36. package/contracts/v1/fixtures/runner-visible-prefix-plan.json +28 -0
  37. package/contracts/v1/fixtures/runner-visible-setup-plan.json +37 -0
  38. package/contracts/v1/fixtures/stream-basic.ndjson +3 -0
  39. package/contracts/v1/fixtures/stream-blocked.ndjson +2 -0
  40. package/contracts/v1/fixtures/stream-submitted-completed.ndjson +3 -0
  41. package/contracts/v1/fixtures/workflow-ask-success.json +67 -0
  42. package/contracts/v1/manifest.json +210 -0
  43. package/contracts/v1/parity-suite.json +635 -0
  44. package/contracts/v1/schemas/agent.schema.json +52 -0
  45. package/contracts/v1/schemas/backend-event.schema.json +74 -0
  46. package/contracts/v1/schemas/backend-request.schema.json +59 -0
  47. package/contracts/v1/schemas/backend-response.schema.json +42 -0
  48. package/contracts/v1/schemas/capabilities.schema.json +33 -0
  49. package/contracts/v1/schemas/command-descriptor.schema.json +31 -0
  50. package/contracts/v1/schemas/command-result.schema.json +99 -0
  51. package/contracts/v1/schemas/manifest.schema.json +69 -0
  52. package/contracts/v1/schemas/response.schema.json +51 -0
  53. package/contracts/v1/schemas/run-result.schema.json +138 -0
  54. package/contracts/v1/schemas/sequence-plan.schema.json +44 -0
  55. package/contracts/v1/schemas/stream-event.schema.json +25 -0
  56. package/dist/codex-chatgpt-control-backend.mjs +5284 -0
  57. package/dist/codex-chatgpt-control.bundle.mjs +6013 -0
  58. package/dist/src/backend/client.d.ts +129 -0
  59. package/dist/src/backend/client.js +415 -0
  60. package/dist/src/backend/protocol.d.ts +74 -0
  61. package/dist/src/backend/protocol.js +128 -0
  62. package/dist/src/backend/session.d.ts +11 -0
  63. package/dist/src/backend/session.js +223 -0
  64. package/dist/src/backend/stdio-server.d.ts +9 -0
  65. package/dist/src/backend/stdio-server.js +88 -0
  66. package/dist/src/browser/attach.d.ts +8 -0
  67. package/dist/src/browser/attach.js +456 -0
  68. package/dist/src/browser/clipboard.d.ts +2 -0
  69. package/dist/src/browser/clipboard.js +26 -0
  70. package/dist/src/browser/downloads.d.ts +7 -0
  71. package/dist/src/browser/downloads.js +29 -0
  72. package/dist/src/browser/page-state.d.ts +17 -0
  73. package/dist/src/browser/page-state.js +66 -0
  74. package/dist/src/client.d.ts +167 -0
  75. package/dist/src/client.js +666 -0
  76. package/dist/src/commands/confirmations.d.ts +9 -0
  77. package/dist/src/commands/confirmations.js +33 -0
  78. package/dist/src/commands/context.d.ts +2 -0
  79. package/dist/src/commands/context.js +32 -0
  80. package/dist/src/commands/doctor.d.ts +16 -0
  81. package/dist/src/commands/doctor.js +120 -0
  82. package/dist/src/commands/files.d.ts +4 -0
  83. package/dist/src/commands/files.js +314 -0
  84. package/dist/src/commands/helpers.d.ts +16 -0
  85. package/dist/src/commands/helpers.js +128 -0
  86. package/dist/src/commands/messages.d.ts +16 -0
  87. package/dist/src/commands/messages.js +538 -0
  88. package/dist/src/commands/modes.d.ts +9 -0
  89. package/dist/src/commands/modes.js +323 -0
  90. package/dist/src/commands/output.d.ts +5 -0
  91. package/dist/src/commands/output.js +31 -0
  92. package/dist/src/commands/registry.d.ts +17 -0
  93. package/dist/src/commands/registry.js +275 -0
  94. package/dist/src/commands/reports.d.ts +14 -0
  95. package/dist/src/commands/reports.js +47 -0
  96. package/dist/src/commands/response-actions.d.ts +2 -0
  97. package/dist/src/commands/response-actions.js +141 -0
  98. package/dist/src/commands/sequence.d.ts +9 -0
  99. package/dist/src/commands/sequence.js +228 -0
  100. package/dist/src/commands/session.d.ts +2 -0
  101. package/dist/src/commands/session.js +25 -0
  102. package/dist/src/commands/threads.d.ts +6 -0
  103. package/dist/src/commands/threads.js +300 -0
  104. package/dist/src/dom/fixtures.d.ts +1 -0
  105. package/dist/src/dom/fixtures.js +8 -0
  106. package/dist/src/dom/menus.d.ts +8 -0
  107. package/dist/src/dom/menus.js +40 -0
  108. package/dist/src/dom/message-format.d.ts +30 -0
  109. package/dist/src/dom/message-format.js +593 -0
  110. package/dist/src/dom/messages.d.ts +40 -0
  111. package/dist/src/dom/messages.js +127 -0
  112. package/dist/src/dom/selectors.d.ts +20 -0
  113. package/dist/src/dom/selectors.js +72 -0
  114. package/dist/src/dom/visible-text.d.ts +5 -0
  115. package/dist/src/dom/visible-text.js +27 -0
  116. package/dist/src/errors.d.ts +32 -0
  117. package/dist/src/errors.js +127 -0
  118. package/dist/src/index.d.ts +34 -0
  119. package/dist/src/index.js +34 -0
  120. package/dist/src/logger.d.ts +15 -0
  121. package/dist/src/logger.js +25 -0
  122. package/dist/src/runner/agent.d.ts +2 -0
  123. package/dist/src/runner/agent.js +17 -0
  124. package/dist/src/runner/index.d.ts +7 -0
  125. package/dist/src/runner/index.js +7 -0
  126. package/dist/src/runner/interruptions.d.ts +3 -0
  127. package/dist/src/runner/interruptions.js +65 -0
  128. package/dist/src/runner/responses.d.ts +30 -0
  129. package/dist/src/runner/responses.js +171 -0
  130. package/dist/src/runner/result.d.ts +3 -0
  131. package/dist/src/runner/result.js +128 -0
  132. package/dist/src/runner/resume.d.ts +11 -0
  133. package/dist/src/runner/resume.js +26 -0
  134. package/dist/src/runner/stream.d.ts +13 -0
  135. package/dist/src/runner/stream.js +66 -0
  136. package/dist/src/runner/types.d.ts +264 -0
  137. package/dist/src/runner/types.js +1 -0
  138. package/dist/src/safety/blockers.d.ts +7 -0
  139. package/dist/src/safety/blockers.js +60 -0
  140. package/dist/src/safety/redaction.d.ts +2 -0
  141. package/dist/src/safety/redaction.js +18 -0
  142. package/dist/src/safety/report-redaction.d.ts +8 -0
  143. package/dist/src/safety/report-redaction.js +74 -0
  144. package/dist/src/safety/risk.d.ts +26 -0
  145. package/dist/src/safety/risk.js +28 -0
  146. package/dist/src/scripts/backend-server.d.ts +2 -0
  147. package/dist/src/scripts/backend-server.js +7 -0
  148. package/dist/src/scripts/check-parity-suite.d.ts +1 -0
  149. package/dist/src/scripts/check-parity-suite.js +10 -0
  150. package/dist/src/scripts/continue-thread.d.ts +27 -0
  151. package/dist/src/scripts/continue-thread.js +388 -0
  152. package/dist/src/scripts/live-smoke/harness.d.ts +11 -0
  153. package/dist/src/scripts/live-smoke/harness.js +151 -0
  154. package/dist/src/scripts/live-smoke/scenarios.d.ts +3 -0
  155. package/dist/src/scripts/live-smoke/scenarios.js +648 -0
  156. package/dist/src/scripts/live-smoke/types.d.ts +53 -0
  157. package/dist/src/scripts/live-smoke/types.js +1 -0
  158. package/dist/src/scripts/live-smoke-module.d.ts +3 -0
  159. package/dist/src/scripts/live-smoke-module.js +2 -0
  160. package/dist/src/scripts/live-smoke.d.ts +1 -0
  161. package/dist/src/scripts/live-smoke.js +54 -0
  162. package/dist/src/scripts/smoke-attach-file.d.ts +1 -0
  163. package/dist/src/scripts/smoke-attach-file.js +39 -0
  164. package/dist/src/scripts/smoke-hi.d.ts +1 -0
  165. package/dist/src/scripts/smoke-hi.js +25 -0
  166. package/dist/src/scripts/smoke-search-open-read.d.ts +1 -0
  167. package/dist/src/scripts/smoke-search-open-read.js +41 -0
  168. package/dist/src/testing/parity-suite.d.ts +12 -0
  169. package/dist/src/testing/parity-suite.js +213 -0
  170. package/dist/src/types.d.ts +495 -0
  171. package/dist/src/types.js +1 -0
  172. package/package.json +81 -0
  173. package/references/agents-runner.md +32 -0
  174. package/references/backend-protocol.md +238 -0
  175. package/references/python-parity.md +168 -0
  176. package/references/responses-adapter.md +28 -0
  177. package/references/safety.md +14 -0
  178. package/references/streaming.md +15 -0
  179. package/references/troubleshooting.md +91 -0
@@ -0,0 +1,238 @@
1
+ # Backend Protocol
2
+
3
+ The local backend is a long-lived language-neutral service. The initial implementation is Node/TypeScript, exposed over stdio NDJSON.
4
+
5
+ This is the contract that keeps Node and Python deeply in sync:
6
+
7
+ ```text
8
+ Node in-process SDK
9
+ Node backend client
10
+ Python SDK
11
+ Future SDKs
12
+ -> backend protocol
13
+ -> compatible browser-control backend
14
+ -> browser bridge
15
+ -> chatgpt.com
16
+ ```
17
+
18
+ The current live backend is Node-backed. A future Python-native backend can replace it by implementing this protocol and passing the same contract fixtures and smoke gates.
19
+
20
+ ## Stdio Transport
21
+
22
+ Each request is one JSON line on stdin. Backend stdout is reserved for protocol JSON only; diagnostics go to stderr.
23
+
24
+ ```json
25
+ {
26
+ "schemaVersion": "chatgpt.browser_control.backend_request.v1",
27
+ "requestId": "req_1",
28
+ "command": "backend.health",
29
+ "payload": {}
30
+ }
31
+ ```
32
+
33
+ Each non-streaming response is one JSON line:
34
+
35
+ ```json
36
+ {
37
+ "schemaVersion": "chatgpt.browser_control.backend_response.v1",
38
+ "requestId": "req_1",
39
+ "ok": true,
40
+ "result": {
41
+ "ok": true,
42
+ "status": "ok"
43
+ }
44
+ }
45
+ ```
46
+
47
+ Protocol errors use the same response envelope:
48
+
49
+ ```json
50
+ {
51
+ "schemaVersion": "chatgpt.browser_control.backend_response.v1",
52
+ "requestId": "req_1",
53
+ "ok": false,
54
+ "error": {
55
+ "code": "unknown_command",
56
+ "message": "Unknown backend command: runner.nope",
57
+ "recoverable": false
58
+ }
59
+ }
60
+ ```
61
+
62
+ Current protocol error codes are:
63
+
64
+ - `invalid_request`
65
+ - `unsupported_schema_version`
66
+ - `unknown_command`
67
+
68
+ Browser-control blockers are not protocol errors. They are normal command or runner results with `status: "blocked"`, `status: "partial"`, or `status: "needs_confirmation"` plus blocker/interruption details.
69
+
70
+ ## Streaming
71
+
72
+ Streaming commands emit backend event lines until `completed` or `error`.
73
+
74
+ ```json
75
+ {
76
+ "schemaVersion": "chatgpt.browser_control.backend_event.v1",
77
+ "requestId": "req_stream",
78
+ "type": "run_item_stream_event",
79
+ "name": "message_completed",
80
+ "item": {
81
+ "type": "message.completed"
82
+ }
83
+ }
84
+ ```
85
+
86
+ The final event contains a normal runner result:
87
+
88
+ ```json
89
+ {
90
+ "schemaVersion": "chatgpt.browser_control.backend_event.v1",
91
+ "requestId": "req_stream",
92
+ "type": "completed",
93
+ "result": {
94
+ "status": "ok",
95
+ "output_text": "hi"
96
+ }
97
+ }
98
+ ```
99
+
100
+ Streaming is milestone streaming only. It does not promise token deltas or OpenAI API stream-event parity.
101
+
102
+ ## Required Backend Commands
103
+
104
+ The backend must support:
105
+
106
+ - lifecycle: `backend.version`, `backend.health`, `backend.capabilities`
107
+ - runner: `runner.run`, `runner.plan`, `runner.stream`
108
+ - Responses adapter: `responses.create`
109
+ - workflows: `ask`, `askInThread`, `askWithFiles`, `askAndDownload`, `runMessages`, `openThread`, `runPlan`
110
+ - reports: `createReport`, `reports.create`, `reports.redact`, `reports.summarize`
111
+ - command discovery: `commands`, `describe`, `help`
112
+ - primitives: `session.bootstrap`, `threads.*`, `messages.*`, `files.*`, `modes.set`, `tools.select`, `response.copy`
113
+
114
+ `session.bootstrap` accepts `existingTab` for explicit reuse of a user-open Chrome tab before any read or prompt step. The wire shape is shared by TypeScript and Python:
115
+
116
+ ```json
117
+ {
118
+ "existingTab": {
119
+ "target": { "type": "selected", "host": "chatgpt" },
120
+ "ifMissing": "block"
121
+ }
122
+ }
123
+ ```
124
+
125
+ Other supported targets are `{ "type": "url", "url": "https://chatgpt.com/c/..." }`, `{ "type": "conversationId", "conversationId": "..." }`, and `{ "type": "tabId", "tabId": "..." }`. Explicit existing-tab reuse blocks by default when no matching tab is open. `ifMissing: "open"` may open URL or conversation-id targets, but selected-tab and tab-id targets remain claim-only because there is no deterministic URL to create.
126
+
127
+ `backend.capabilities` is the source of truth for supported commands, transports, and stream modes. The current backend advertises:
128
+
129
+ ```json
130
+ {
131
+ "protocolVersion": "chatgpt.browser_control.backend_request.v1",
132
+ "transports": ["stdio"],
133
+ "streaming": {
134
+ "modes": ["ndjson"],
135
+ "tokenDeltas": false
136
+ }
137
+ }
138
+ ```
139
+
140
+ ## HTTP/SSE Status
141
+
142
+ HTTP/SSE is deferred in this phase. Stdio NDJSON is the default long-lived local transport because it has no port allocation, no browser-origin surface, and no local network security prompt. It also covers the current streaming requirement through backend event lines.
143
+
144
+ Any future HTTP/SSE implementation must use the same request, response, event, capabilities, fixture, and conformance shapes. It should add `http` and `sse` capabilities only after transport-specific tests pass.
145
+
146
+ ## Runtime Boundary
147
+
148
+ Python is a native SDK facade over the protocol. The current browser-control runtime is still Node-backed.
149
+
150
+ An ordinary shell can launch:
151
+
152
+ ```bash
153
+ node ../node/dist/codex-chatgpt-control-backend.mjs
154
+ ```
155
+
156
+ That is enough to validate protocol shape, command dispatch, contracts, and blocker handling. It is not enough to guarantee live ChatGPT control, because a plain subprocess does not automatically inherit Codex's JavaScript `globalThis.agent` browser bridge.
157
+
158
+ For live browser control, the backend process must have access to a compatible browser bridge through one of the backend runtime options:
159
+
160
+ - Codex-hosted JavaScript runtime with `globalThis.agent`.
161
+ - Explicit `RuntimeEnv.browser` or `RuntimeEnv.page` in a future embedding.
162
+ - A future Python-native/native-host/CDP backend that implements this same protocol.
163
+
164
+ Important: in Codex, `globalThis.agent` is not present until the Chrome plugin runtime is bootstrapped. Do not diagnose bridge availability by checking `globalThis.agent` in an ordinary shell or before calling the Chrome plugin's `setupBrowserRuntime({ globals: globalThis })`.
165
+
166
+ The live Chrome bootstrap is:
167
+
168
+ ```js
169
+ const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/26.602.40724/scripts/browser-client.mjs");
170
+ await setupBrowserRuntime({ globals: globalThis });
171
+ globalThis.browser = await agent.browsers.get("extension");
172
+ ```
173
+
174
+ ## Ordinary-Shell Smoke
175
+
176
+ Run from `packages/node`:
177
+
178
+ ```bash
179
+ npm run bundle:backend
180
+ ```
181
+
182
+ Run from `packages/python`:
183
+
184
+ ```bash
185
+ python scripts/live_smoke.py --mode ordinary-shell
186
+ ```
187
+
188
+ 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.
189
+
190
+ ## Browser-Bridge Smoke
191
+
192
+ Run only when intentionally operating a backend with live browser access:
193
+
194
+ ```bash
195
+ python scripts/live_smoke.py --mode browser-bridge
196
+ ```
197
+
198
+ Use `CHATGPT_BROWSER_BACKEND_COMMAND` or `--backend-command` when the bridge-enabled backend is not the default bundle:
199
+
200
+ ```bash
201
+ CHATGPT_BROWSER_BACKEND_COMMAND="node /absolute/path/to/bridge-enabled-backend.mjs" \
202
+ python scripts/live_smoke.py --mode browser-bridge
203
+ ```
204
+
205
+ When the bridge-enabled backend is running inside an active Codex Chrome-plugin JS execution rather than a standalone process, run Python through the stdio relay:
206
+
207
+ ```bash
208
+ CHATGPT_BROWSER_BACKEND_HTTP_URL=http://127.0.0.1:<relay-port> \
209
+ python scripts/live_smoke.py \
210
+ --mode browser-bridge \
211
+ --backend-command "node scripts/http_stdio_relay.mjs"
212
+ ```
213
+
214
+ The bridge-hosted JS execution must remain active for the duration of the Python smoke. The relay path is:
215
+
216
+ ```text
217
+ Python SDK -> stdio relay -> bridge-hosted Node backend -> Codex Chrome bridge -> ChatGPT
218
+ ```
219
+
220
+ The smoke covers `runner.run`, `runner.run_streamed`, `responses.create`, named `run_plan`, and redacted `reports.create`. It writes redacted JSON summaries and does not persist raw prompt/response content by default.
221
+
222
+ ## Contract Fixtures
223
+
224
+ Shared fixtures live under:
225
+
226
+ ```text
227
+ contracts/v1/
228
+ ```
229
+
230
+ Required gates:
231
+
232
+ ```bash
233
+ npm run contract:validate
234
+ npm run parity:fixtures
235
+ npm run test:backend-conformance
236
+ ```
237
+
238
+ Python must also load and round-trip the same fixtures through Pydantic models. Any future backend implementation should pass these fixtures before claiming compatibility.
@@ -0,0 +1,168 @@
1
+ # Python Parity
2
+
3
+ The Python package is a parity client over the TypeScript browser-control runtime. Keep browser automation owned by `packages/node/`; keep Python synchronized through the versioned wire contract in `contracts/v1/`.
4
+
5
+ ## Contract
6
+
7
+ - Shared fixtures live in `contracts/v1/fixtures/`.
8
+ - `npm run contract:validate` validates every fixture against JSON Schema.
9
+ - `npm run parity:fixtures` enforces fixture shape, stream settlement, and wire-field casing.
10
+ - `npm run parity:suite` validates `contracts/v1/parity-suite.json`, which ties every public backend command and fixture to TypeScript tests, Python tests, docs, and deterministic CI gates.
11
+ - Python tests load the same manifest and round-trip every JSON fixture through Pydantic models.
12
+
13
+ Wire fields stay TypeScript-compatible. Python exposes idiomatic aliases:
14
+
15
+ | Wire | Python |
16
+ | --- | --- |
17
+ | `finalOutput` | `final_output` |
18
+ | `newItems` | `new_items` |
19
+ | `activeAgentName` | `active_agent_name` |
20
+ | `lastAgentName` | `last_agent_name` |
21
+ | `nextStepId` | `next_step_id` |
22
+
23
+ ## Sync Python
24
+
25
+ ```python
26
+ from codex_chatgpt_control import ChatGPT, NodeSidecarTransport
27
+
28
+ chatgpt = ChatGPT(
29
+ transport=NodeSidecarTransport(
30
+ command=["node", "dist/codex-chatgpt-control-backend.mjs"]
31
+ )
32
+ )
33
+ agent = chatgpt.agent(name="reviewer", instructions="Review deeply.")
34
+ result = chatgpt.runner.run(agent, input="Reply with hi.")
35
+
36
+ print(result.output_text)
37
+ print(result.final_output)
38
+ ```
39
+
40
+ ## Async Python
41
+
42
+ ```python
43
+ from codex_chatgpt_control import AsyncChatGPT
44
+
45
+ chatgpt = AsyncChatGPT(transport=my_async_transport)
46
+ agent = chatgpt.agent(name="reviewer", instructions="Review deeply.")
47
+ result = await chatgpt.runner.run(agent, input="Reply with hi.")
48
+ ```
49
+
50
+ ## Responses Fixture Shape
51
+
52
+ ```python
53
+ from codex_chatgpt_control import ChatGPTResponse
54
+
55
+ response = ChatGPTResponse.from_wire(payload["response"])
56
+ unsupported = response.unsupported_fields
57
+ ```
58
+
59
+ Unsupported OpenAI API-only fields stay explicit in `browser_control.unsupported[]`; the Python adapter must not submit them silently.
60
+
61
+ ## Streaming
62
+
63
+ `stream-*.ndjson` fixtures are milestone streams. They are not token streams. The final `completed` event contains a normal `ChatGPTRunResult` wire object, including blockers when the run cannot proceed.
64
+
65
+ ```python
66
+ from codex_chatgpt_control import ChatGPTStreamEvent
67
+
68
+ event = ChatGPTStreamEvent.from_wire(payload)
69
+ if event.type == "completed" and event.result is not None:
70
+ print(event.result.status)
71
+ ```
72
+
73
+ ## Required Gates
74
+
75
+ Run from `packages/node`:
76
+
77
+ ```bash
78
+ npm run bundle:backend
79
+ npm run contract:validate
80
+ npm run parity:fixtures
81
+ npm run parity:suite
82
+ npm run test:backend-conformance
83
+ npm test -- tests/unit/contract-fixtures.test.ts
84
+ ```
85
+
86
+ Run from `packages/python`:
87
+
88
+ ```bash
89
+ python -m pip install -e ".[dev]"
90
+ python -m unittest discover -s tests
91
+ python -m compileall -q src
92
+ python -m pyright --pythonpath "$(which python)" src tests
93
+ python scripts/live_smoke.py --mode ordinary-shell
94
+ ```
95
+
96
+ ## Backend Runtime
97
+
98
+ Python is a native SDK facade over the local backend protocol. The initial browser-control runtime is still the TypeScript backend service:
99
+
100
+ - `dist/codex-chatgpt-control-backend.mjs` is the stdio backend bundle.
101
+ - `BackendClient` and `StdioBackendTransport` keep Python backend calls long-lived.
102
+ - `NodeSidecarTransport.run(...)` remains as a compatibility wrapper over backend `runner.run`.
103
+ - Ordinary-shell smoke passes when browser-required calls return structured `browser_bridge_unavailable`.
104
+ - Browser-bridge runtime smoke remains explicitly gated because it can operate a real ChatGPT session.
105
+
106
+ ## Browser-Bridge Smoke
107
+
108
+ Run this only when you intentionally want Python to drive a live backend with browser access:
109
+
110
+ ```bash
111
+ python scripts/live_smoke.py --mode browser-bridge
112
+ ```
113
+
114
+ The command covers:
115
+
116
+ - `runner.run` new ask/read.
117
+ - `runner.run_streamed` milestone streaming.
118
+ - `responses.create` basic.
119
+ - `run_plan` named `new-ask-read`.
120
+ - `reports.create` redacted report output.
121
+
122
+ The default backend command is the Node stdio bundle:
123
+
124
+ ```text
125
+ ../node/dist/codex-chatgpt-control-backend.mjs
126
+ ```
127
+
128
+ That default is enough for protocol validation and structured blockers, but a plain subprocess cannot inherit Codex's JavaScript `globalThis.agent` browser bridge. For a true live browser pass, point Python at a stdio backend command that already runs in a bridge-enabled host:
129
+
130
+ ```bash
131
+ CHATGPT_BROWSER_BACKEND_COMMAND="node /absolute/path/to/bridge-enabled-backend.mjs" \
132
+ python scripts/live_smoke.py --mode browser-bridge
133
+ ```
134
+
135
+ ### Codex Chrome Plugin Relay
136
+
137
+ When the live backend is hosted inside the Codex Chrome plugin runtime, do not test bridge availability from a normal shell or an unbootstrapped Node REPL. First initialize the Chrome runtime:
138
+
139
+ ```js
140
+ const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/26.602.40724/scripts/browser-client.mjs");
141
+ await setupBrowserRuntime({ globals: globalThis });
142
+ globalThis.browser = await agent.browsers.get("extension");
143
+ ```
144
+
145
+ Then run the backend server inside that active JS execution context and point Python at the stdio-to-HTTP relay:
146
+
147
+ ```bash
148
+ CHATGPT_BROWSER_BACKEND_HTTP_URL=http://127.0.0.1:<relay-port> \
149
+ python scripts/live_smoke.py \
150
+ --mode browser-bridge \
151
+ --backend-command "node scripts/http_stdio_relay.mjs"
152
+ ```
153
+
154
+ Keep the bridge-hosted JS execution active while Python runs. If that JS execution returns first, the browser client no longer has an active execution context and operations can fail with `node_repl exec context not found`.
155
+
156
+ This is the intended live test chain:
157
+
158
+ ```text
159
+ Python SDK -> scripts/http_stdio_relay.mjs -> bridge-hosted Node backend -> Codex Chrome bridge -> ChatGPT
160
+ ```
161
+
162
+ Smoke output is a redacted JSON summary. It reports output matches and lengths, not raw prompts or raw responses. Exit codes are:
163
+
164
+ | Code | Meaning |
165
+ | --- | --- |
166
+ | `0` | All browser-bridge scenarios passed. |
167
+ | `1` | At least one scenario failed unexpectedly. |
168
+ | `2` | Scenarios recorded documented blockers such as `browser_bridge_unavailable`, `login_required`, or `selector_drift`. |
@@ -0,0 +1,28 @@
1
+ # Responses Adapter
2
+
3
+ `chatgpt.responses.create()` is a narrow convenience wrapper around visible ChatGPT browser control. It returns a Responses-shaped object with `object: "chatgpt.browser.response"`, but it is not the OpenAI Responses API and does not support hidden model controls.
4
+
5
+ Accepted fields:
6
+
7
+ - `input`
8
+ - `thread`
9
+ - `attachments`
10
+ - `mode`
11
+ - `tools`
12
+ - `text.format`
13
+ - `stream: false`
14
+ - `report`
15
+ - `instructions` only with `instructionsMode: "visible_prefix"`
16
+
17
+ Rejected API-only fields return `status: "unsupported"` before any prompt is submitted. The response includes `browser_control.unsupported[]` with `path`, `reason`, and `alternative`.
18
+
19
+ ```ts
20
+ const response = await chatgpt.responses.create({
21
+ input: "Summarize the latest plan.",
22
+ thread: { type: "conversationId", conversationId: "abc-123" },
23
+ text: { format: "markdown" },
24
+ stream: false
25
+ });
26
+ ```
27
+
28
+ Use `chatgpt.runner.run()` for lower-level browser-control workflows, multi-step command planning, attachments, downloads, reports, and explicit interruption handling.
@@ -0,0 +1,14 @@
1
+ # Safety Rules
2
+
3
+ - Treat ChatGPT conversation content as private unless the user explicitly says otherwise.
4
+ - Do not save raw ChatGPT conversation content to memory.
5
+ - Do not send ChatGPT content to external model tools without explicit user approval.
6
+ - Do not create shared links automatically.
7
+ - Do not delete, archive, move, or share threads without exact confirmation.
8
+ - Do not connect or disconnect ChatGPT apps/connectors automatically.
9
+ - Do not change account settings, memory, custom instructions, data controls, or workspace state automatically.
10
+ - Do not read browser cookies, localStorage, sessionStorage, auth tokens, hidden headers, or private network request internals.
11
+ - Prefer DOM/UI automation and official APIs over private endpoint replay.
12
+ - Runner `instructions` are visible prompt text unless `instructionsMode: "metadata_only"` keeps them local. Do not imply hidden system-message semantics.
13
+ - `chatgpt.responses.create()` is not the OpenAI Responses API. Reject API-only model controls before submitting a prompt.
14
+ - Runner streaming is milestone-only. Do not claim token-level streaming or API stream event parity.
@@ -0,0 +1,15 @@
1
+ # Runner Streaming
2
+
3
+ `chatgpt.runner.run(agent, input, { stream: true })` returns an async iterable of runner milestone events plus a `completed` promise.
4
+
5
+ ```ts
6
+ const stream = chatgpt.runner.run(agent, "Reply with hi", { stream: true });
7
+
8
+ for await (const event of stream) {
9
+ console.log(event.name, event.item.type);
10
+ }
11
+
12
+ const result = await stream.completed;
13
+ ```
14
+
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.
@@ -0,0 +1,91 @@
1
+ # Troubleshooting
2
+
3
+ ## `browser_bridge_unavailable`
4
+
5
+ The backend process does not have access to a browser bridge. This is expected when a live-smoke command runs from an ordinary shell: it proves the protocol stayed alive and surfaced a structured blocker.
6
+
7
+ The structured blocker should include `code: "codex_chrome_bridge_unavailable"` plus `blocker.remediation[]`. Agents should read those remediation steps before asking the user to restart Chrome or change permissions.
8
+
9
+ Do not conclude that Chrome or the extension is broken from a plain shell result, or from checking `globalThis.agent` before the Chrome plugin runtime is initialized. For a true Codex Chrome-plugin live run, bootstrap the runtime first:
10
+
11
+ ```js
12
+ const { setupBrowserRuntime } = await import("/example/user/.codex/plugins/cache/openai-bundled/chrome/26.602.40724/scripts/browser-client.mjs");
13
+ await setupBrowserRuntime({ globals: globalThis });
14
+ globalThis.browser = await agent.browsers.get("extension");
15
+ ```
16
+
17
+ If the command was intentionally running in a bridge-enabled host and still returns this blocker, verify that the Codex Chrome extension is installed and enabled, then restart Chrome or Codex if the backend is still unavailable. Do not keep retrying the same attach path indefinitely.
18
+
19
+ For Python live bridge smokes, a plain Python-spawned Node subprocess does not inherit Codex's in-process bridge. Use the relay into an active bridge-hosted backend:
20
+
21
+ ```bash
22
+ CHATGPT_BROWSER_BACKEND_HTTP_URL=http://127.0.0.1:<relay-port> \
23
+ python scripts/live_smoke.py \
24
+ --mode browser-bridge \
25
+ --backend-command "node scripts/http_stdio_relay.mjs"
26
+ ```
27
+
28
+ Keep the bridge-hosted JS execution active while Python runs. If that call returns first, browser operations can fail with `node_repl exec context not found`.
29
+
30
+ ## `login_required`
31
+
32
+ The user needs to sign in to ChatGPT in Chrome. Stop and ask the user to complete login.
33
+
34
+ ## `captcha`
35
+
36
+ Stop. Do not attempt bypass.
37
+
38
+ ## `rate_limit`
39
+
40
+ Return the visible limit text and stop unless the user asks to wait or try a different path.
41
+
42
+ ## `selector_drift`
43
+
44
+ The ChatGPT UI changed or the page loaded an unexpected surface. Return visible menu/button candidates and a screenshot/DOM summary if available.
45
+
46
+ Runner results surface this as `interruptions[0].type === "selector_drift"` with `blocker.candidates` when visible candidates were available. Do not retry automatically; ask the user or update selectors.
47
+
48
+ ## File Upload Permission
49
+
50
+ File uploads have two separate permission gates:
51
+
52
+ 1. Codex app settings for Chrome uploads must allow `chatgpt.com`, or uploads must be set to always allow.
53
+ 2. Chrome's extension details page for the Codex extension may also need `Allow access to file URLs`.
54
+
55
+ If `fileChooser.setFiles` returns `Not allowed`, the ChatGPT chooser was reached but Chrome refused the local file handoff. Check both gates before retrying.
56
+
57
+ Agent-facing remediation text should name both settings:
58
+
59
+ > File upload is blocked by Chrome/Codex permissions. Ask the user to enable both: Codex Settings > Computer Use > Chrome > Permissions > Uploads, and Chrome chrome://extensions > Codex extension > Details > Allow access to file URLs. Then retry.
60
+
61
+ ## Clipboard Unavailable
62
+
63
+ `response.copy` falls back to DOM text extraction when the macOS system clipboard does not change.
64
+
65
+ ## Flattened Or Unreadable Response Capture
66
+
67
+ Use `readLatest({ format: "markdown" })`, `copyLatest()`, or the default SDK `read: true` path for human-readable responses. Use `format: "normalized_text"` only for exact-string assertions or polling. Check `data.source`, `data.fidelity`, and `data.warnings`: clipboard output is highest fidelity, while DOM Markdown is semantic reconstruction. If Markdown capture degrades, return the command warning and save the structured `blocks` or diagnostic `html` representation instead of silently writing flattened text as a Markdown report.
68
+
69
+ ## Response Branch Ambiguity
70
+
71
+ When ChatGPT exposes previous/next response controls, `readLatest` and `copyLatest` include `branch.current`, `branch.total`, `actions`, and `thoughtDurationText` when visible. If the branch state is missing but the user expects a rerun or edited-message branch, reload the thread and read again before claiming to have captured the latest answer.
72
+
73
+ ## Download Unavailable
74
+
75
+ The command only downloads visible files with a download affordance. If no download control exists, ask ChatGPT to create or expose the file again.
76
+
77
+ ## Redacted Reports
78
+
79
+ `createReport`, `chatgpt.reports.*`, SDK workflow reports, and live-smoke reports redact raw prompt/response/file content by default. Use `includeContent: true` only when the user explicitly asks to persist raw content.
80
+
81
+ ## Responses Adapter Unsupported Fields
82
+
83
+ `chatgpt.responses.create()` rejects OpenAI API-only fields such as `model`, `temperature`, `logprobs`, `previous_response_id`, `store`, and `max_output_tokens` before submitting a prompt. Inspect `browser_control.unsupported[]` for `path`, `reason`, and `alternative`.
84
+
85
+ ## Runner Milestone Streaming
86
+
87
+ `chatgpt.runner.run(agent, input, { stream: true })` returns milestone events and `stream.completed`. It does not stream token deltas. If an agent expects API stream events, use `event.name` and `event.item.type` instead.
88
+
89
+ ## Doctor Preflight
90
+
91
+ Run `doctor({ check: ["bridge", "login", "upload", "download", "clipboard"] })` before long workflows when the browser state or permissions are uncertain. Upload readiness may remain `unknown` until a live attach attempt, but the remediation should still name both upload permission gates.