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.
- package/CHANGELOG.md +42 -0
- package/README.md +43 -0
- package/contracts/v1/fixtures/backend-capabilities.json +40 -1
- package/contracts/v1/fixtures/backend-compatibility.json +21 -0
- package/contracts/v1/fixtures/backend-version.json +6 -1
- package/contracts/v1/fixtures/download-blocked-by-browser.json +22 -0
- package/contracts/v1/fixtures/download-receipt-failed.json +22 -0
- package/contracts/v1/fixtures/download-receipt-timeout.json +22 -0
- package/contracts/v1/fixtures/journal-rpc-indeterminate.json +32 -0
- package/contracts/v1/fixtures/journal-rpc-unsupported-platform.json +32 -0
- package/contracts/v1/fixtures/journal-runtime-unavailable.json +32 -0
- package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
- package/contracts/v1/fixtures/operation-action.json +13 -0
- package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
- package/contracts/v1/fixtures/operation-blocker.json +10 -0
- package/contracts/v1/fixtures/operation-collect-request.json +17 -0
- package/contracts/v1/fixtures/operation-collect-result.json +42 -0
- package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
- package/contracts/v1/fixtures/operation-control-request.json +18 -0
- package/contracts/v1/fixtures/operation-control-result.json +31 -0
- package/contracts/v1/fixtures/operation-event.json +18 -0
- package/contracts/v1/fixtures/operation-handle.json +10 -0
- package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
- package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
- package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
- package/contracts/v1/fixtures/operation-receipt.json +32 -0
- package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
- package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
- package/contracts/v1/fixtures/operation-request.json +33 -0
- package/contracts/v1/fixtures/operation-state.json +144 -0
- package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
- package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
- package/contracts/v1/fixtures/operation-submit-result.json +16 -0
- package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
- package/contracts/v1/manifest.json +185 -1
- package/contracts/v1/parity-suite.json +106 -8
- package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
- package/contracts/v1/schemas/backend-request.schema.json +8 -2
- package/contracts/v1/schemas/capabilities.schema.json +81 -1
- package/contracts/v1/schemas/manifest.schema.json +57 -0
- package/contracts/v1/schemas/operation-action.schema.json +155 -0
- package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
- package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
- package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
- package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
- package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
- package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
- package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
- package/contracts/v1/schemas/operation-event.schema.json +756 -0
- package/contracts/v1/schemas/operation-handle.schema.json +55 -0
- package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
- package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
- package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
- package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
- package/contracts/v1/schemas/operation-request.schema.json +158 -0
- package/contracts/v1/schemas/operation-state.schema.json +691 -0
- package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
- package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
- package/contracts/v1/surface-drift-policy.json +50 -0
- package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
- package/dist/codex-chatgpt-control-backend.mjs +45882 -9361
- package/dist/codex-chatgpt-control-journal.mjs +5547 -0
- package/dist/codex-chatgpt-control-live-smoke.bundle.mjs +51677 -0
- package/dist/codex-chatgpt-control-release-canary.bundle.mjs +52104 -0
- package/dist/codex-chatgpt-control.bundle.mjs +48392 -10260
- package/dist/src/backend/client.d.ts +103 -2
- package/dist/src/backend/client.js +1436 -97
- package/dist/src/backend/compatibility.d.ts +12 -0
- package/dist/src/backend/compatibility.js +208 -0
- package/dist/src/backend/protocol.d.ts +84 -1
- package/dist/src/backend/protocol.js +31 -5
- package/dist/src/backend/runtime-identity.d.ts +10 -0
- package/dist/src/backend/runtime-identity.js +141 -0
- package/dist/src/backend/session.d.ts +10 -2
- package/dist/src/backend/session.js +478 -7
- package/dist/src/backend/stdio-server.d.ts +4 -1
- package/dist/src/backend/stdio-server.js +240 -28
- package/dist/src/browser/active-composer-file-input.d.ts +9 -0
- package/dist/src/browser/active-composer-file-input.js +33 -0
- package/dist/src/browser/attach.d.ts +4 -1
- package/dist/src/browser/attach.js +136 -28
- package/dist/src/browser/chatgpt-url.d.ts +2 -0
- package/dist/src/browser/chatgpt-url.js +9 -0
- package/dist/src/browser/downloads.d.ts +17 -1
- package/dist/src/browser/downloads.js +150 -28
- package/dist/src/browser/page-state.js +6 -0
- package/dist/src/client.d.ts +43 -0
- package/dist/src/client.js +1279 -59
- package/dist/src/commands/artifacts.js +6 -4
- package/dist/src/commands/chat-popover.d.ts +37 -0
- package/dist/src/commands/chat-popover.js +537 -0
- package/dist/src/commands/configuration.d.ts +5 -0
- package/dist/src/commands/configuration.js +134 -21
- package/dist/src/commands/context.js +10 -3
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +27 -4
- package/dist/src/commands/experience.d.ts +5 -0
- package/dist/src/commands/experience.js +104 -21
- package/dist/src/commands/files.js +14 -33
- package/dist/src/commands/modes.js +85 -21
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +669 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +19 -6
- package/dist/src/commands/work.js +31 -0
- package/dist/src/dom/composer-text.d.ts +6 -0
- package/dist/src/dom/composer-text.js +85 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +9 -0
- package/dist/src/operations/artifact-output.d.ts +69 -0
- package/dist/src/operations/artifact-output.js +1748 -0
- package/dist/src/operations/artifact-stream.d.ts +19 -0
- package/dist/src/operations/artifact-stream.js +35 -0
- package/dist/src/operations/artifact-transfer.d.ts +149 -0
- package/dist/src/operations/artifact-transfer.js +1312 -0
- package/dist/src/operations/browser-adapter.d.ts +167 -0
- package/dist/src/operations/browser-adapter.js +2103 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1256 -0
- package/dist/src/operations/browser-target.d.ts +105 -0
- package/dist/src/operations/browser-target.js +531 -0
- package/dist/src/operations/canonical.d.ts +4 -0
- package/dist/src/operations/canonical.js +412 -0
- package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
- package/dist/src/operations/chatgpt-runtime.js +1232 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1145 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/configuration-routing.d.ts +4 -0
- package/dist/src/operations/configuration-routing.js +14 -0
- package/dist/src/operations/control.d.ts +372 -0
- package/dist/src/operations/control.js +1498 -0
- package/dist/src/operations/file-identity.d.ts +33 -0
- package/dist/src/operations/file-identity.js +153 -0
- package/dist/src/operations/handle.d.ts +19 -0
- package/dist/src/operations/handle.js +727 -0
- package/dist/src/operations/index.d.ts +25 -0
- package/dist/src/operations/index.js +25 -0
- package/dist/src/operations/journal-authority.d.ts +12 -0
- package/dist/src/operations/journal-authority.js +1 -0
- package/dist/src/operations/journal-rpc-client.d.ts +12 -0
- package/dist/src/operations/journal-rpc-client.js +225 -0
- package/dist/src/operations/journal-rpc-protocol.d.ts +32 -0
- package/dist/src/operations/journal-rpc-protocol.js +214 -0
- package/dist/src/operations/journal-rpc-server.d.ts +15 -0
- package/dist/src/operations/journal-rpc-server.js +272 -0
- package/dist/src/operations/journal.d.ts +143 -0
- package/dist/src/operations/journal.js +1957 -0
- package/dist/src/operations/production-attachments.d.ts +118 -0
- package/dist/src/operations/production-attachments.js +1035 -0
- package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
- package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
- package/dist/src/operations/production-chatgpt-attachments.d.ts +80 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1639 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1544 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +856 -0
- package/dist/src/operations/production-work-steer.d.ts +203 -0
- package/dist/src/operations/production-work-steer.js +1218 -0
- package/dist/src/operations/recovery.d.ts +80 -0
- package/dist/src/operations/recovery.js +174 -0
- package/dist/src/operations/runtime-adapter.d.ts +126 -0
- package/dist/src/operations/runtime-adapter.js +802 -0
- package/dist/src/operations/send-once.d.ts +224 -0
- package/dist/src/operations/send-once.js +1081 -0
- package/dist/src/operations/service.d.ts +286 -0
- package/dist/src/operations/service.js +2831 -0
- package/dist/src/operations/staging.d.ts +136 -0
- package/dist/src/operations/staging.js +630 -0
- package/dist/src/operations/state-machine.d.ts +29 -0
- package/dist/src/operations/state-machine.js +2047 -0
- package/dist/src/operations/submission.d.ts +423 -0
- package/dist/src/operations/submission.js +1676 -0
- package/dist/src/operations/transactional-chat-power.d.ts +17 -0
- package/dist/src/operations/transactional-chat-power.js +212 -0
- package/dist/src/operations/turn-ownership.d.ts +202 -0
- package/dist/src/operations/turn-ownership.js +700 -0
- package/dist/src/operations/types.d.ts +454 -0
- package/dist/src/operations/types.js +20 -0
- package/dist/src/operations/wire-requests.d.ts +21 -0
- package/dist/src/operations/wire-requests.js +396 -0
- package/dist/src/operations/wire-results.d.ts +97 -0
- package/dist/src/operations/wire-results.js +818 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +19 -1
- package/dist/src/runner/result.js +227 -55
- package/dist/src/runner/types.d.ts +12 -0
- package/dist/src/runtime/command-routing.d.ts +149 -0
- package/dist/src/runtime/command-routing.js +431 -0
- package/dist/src/runtime/coordinated-browser.d.ts +24 -0
- package/dist/src/runtime/coordinated-browser.js +315 -0
- package/dist/src/runtime/coordinated-page.d.ts +41 -0
- package/dist/src/runtime/coordinated-page.js +651 -0
- package/dist/src/runtime/operation-context.d.ts +142 -0
- package/dist/src/runtime/operation-context.js +410 -0
- package/dist/src/runtime/runtime-session.d.ts +95 -0
- package/dist/src/runtime/runtime-session.js +314 -0
- package/dist/src/runtime/tab-coordinator.d.ts +200 -0
- package/dist/src/runtime/tab-coordinator.js +1200 -0
- package/dist/src/runtime/value-boundaries.d.ts +18 -0
- package/dist/src/runtime/value-boundaries.js +49 -0
- package/dist/src/safety/blockers.js +8 -1
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/capture-surface-profile.js +7 -2
- package/dist/src/scripts/journal-server.d.ts +2 -0
- package/dist/src/scripts/journal-server.js +37 -0
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +42 -1
- package/dist/src/scripts/live-smoke/scenarios.js +147 -30
- package/dist/src/scripts/live-smoke/transactional.d.ts +17 -0
- package/dist/src/scripts/live-smoke/transactional.js +254 -0
- package/dist/src/scripts/live-smoke/types.d.ts +3 -0
- package/dist/src/scripts/release-canary-module.js +17 -4
- package/dist/src/types.d.ts +38 -1
- package/package.json +8 -3
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/2026-09-06-journal-service.md +140 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +176 -0
- package/references/python-parity.md +137 -0
- package/references/responses-adapter.md +19 -0
- 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.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Journal service for restricted browser hosts
|
|
3
|
+
date: 2026-09-06
|
|
4
|
+
type: runbook
|
|
5
|
+
status: implemented
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Journal service for restricted browser hosts
|
|
9
|
+
|
|
10
|
+
Transactional workflows need a real Node process to own journal files and locks.
|
|
11
|
+
Some browser hosts expose filesystem access but no process identity or local
|
|
12
|
+
network sockets. Configure the explicit private-file journal service in these
|
|
13
|
+
hosts. Browser operations and prepared Send capabilities remain in the browser
|
|
14
|
+
host; only journal storage and signing use the service.
|
|
15
|
+
|
|
16
|
+
This private-file transport supports macOS and Linux. Windows is explicitly
|
|
17
|
+
rejected with `journal_rpc_unsupported_platform` before filesystem access;
|
|
18
|
+
POSIX mode bits do not establish Windows ownership or ACL protection. This
|
|
19
|
+
limit applies to the new service transport. The existing local Node journal
|
|
20
|
+
path and ordinary Node/Python Windows support retain their current behavior.
|
|
21
|
+
|
|
22
|
+
Start the packaged service in an ordinary Node terminal. Supply a persistent,
|
|
23
|
+
absolute state root and a **new** absolute session directory. On macOS use the
|
|
24
|
+
canonical `/private/tmp` spelling when choosing a temporary directory.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
node /absolute/plugin/runtime/node/codex-chatgpt-control-journal.mjs \
|
|
28
|
+
--state-root /absolute/private/journal-state \
|
|
29
|
+
--directory /absolute/private/new-journal-session
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For a source checkout, build with `npm run bundle:journal` and use
|
|
33
|
+
`dist/codex-chatgpt-control-journal.mjs`. The service prints only a readiness
|
|
34
|
+
result. Keep `new-journal-session/connection.json` private: it contains the
|
|
35
|
+
session authentication secret. Never include its contents in a prompt, report,
|
|
36
|
+
issue, or log.
|
|
37
|
+
|
|
38
|
+
After initializing the supported Chrome bridge, create the browser client:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const chatgpt = createChatGPT({
|
|
42
|
+
agent,
|
|
43
|
+
operations: {
|
|
44
|
+
journalService: {
|
|
45
|
+
descriptorPath: "/absolute/private/new-journal-session/connection.json"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const operationId = crypto.randomUUID();
|
|
50
|
+
const submitted = await chatgpt.ask({
|
|
51
|
+
operationId,
|
|
52
|
+
prompt: "Reply with exactly JOURNAL_OK",
|
|
53
|
+
experience: "chat",
|
|
54
|
+
configuration: { effort: "High" },
|
|
55
|
+
files: [],
|
|
56
|
+
wait: false
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Do not set `operations.stateRoot` together with `journalService`: the service
|
|
61
|
+
owns the state root. Without `journalService`, the existing local Node journal
|
|
62
|
+
path remains unchanged. The client never starts a hidden process or falls back
|
|
63
|
+
to another transport. `timeoutMs` on the connection bounds each journal call;
|
|
64
|
+
it is not a deadline for the complete browser workflow.
|
|
65
|
+
|
|
66
|
+
The service uses owner-only directories and files, authenticated encrypted
|
|
67
|
+
request/response envelopes, bounded request admission, and replay checks. Journal
|
|
68
|
+
signing keys remain in the service. It uses the real Node PID and existing lock
|
|
69
|
+
liveness checks. The connection secret grants journal authority to its holder;
|
|
70
|
+
this is a same-user local service, not a boundary against a hostile process
|
|
71
|
+
running as that same user.
|
|
72
|
+
|
|
73
|
+
## Recovery
|
|
74
|
+
|
|
75
|
+
Stop the owned service with Ctrl-C or SIGTERM. To restart, retain the state root
|
|
76
|
+
and choose another new session directory, then create a new browser client with
|
|
77
|
+
that descriptor. Existing operation IDs, handles and completed receipts remain
|
|
78
|
+
valid. Retry an unconfirmed Send with the **same operation ID and unchanged
|
|
79
|
+
request**. After authenticating that request and its durable Send action, a fresh
|
|
80
|
+
client can reconcile the saved `new_pending` target through an observe-only
|
|
81
|
+
path. It requires the exact saved provider/browser/tab, its blank-task anchor,
|
|
82
|
+
a complete persisted pre-Send baseline, and exactly one new user turn whose
|
|
83
|
+
normalized text matches the requested prompt. The service then records the
|
|
84
|
+
proved conversation and collects its response without another Send.
|
|
85
|
+
|
|
86
|
+
This path never resolves `target: new` again, switches surfaces, restores
|
|
87
|
+
configuration, uploads files, or recomposes the prompt. A saved pending target
|
|
88
|
+
without a durable Send is blocked before browser access. Generic `collect` and
|
|
89
|
+
control calls still reject `new_pending` targets; use same-request submit/ask
|
|
90
|
+
reconciliation first. Missing tabs, unavailable baselines, different prompts,
|
|
91
|
+
and multiple new user turns remain blockers. Fixed or established targets also
|
|
92
|
+
require their exact saved conversation identity. Completed receipts replay
|
|
93
|
+
without browser access.
|
|
94
|
+
|
|
95
|
+
An exact saved tab and matching prompt do not provide provider navigation
|
|
96
|
+
history. Manual navigation on that tab to another conversation containing the
|
|
97
|
+
identical single prompt is observationally indistinguishable from the intended
|
|
98
|
+
Send. Do not navigate or repurpose the operation's tab while recovering it.
|
|
99
|
+
|
|
100
|
+
A missing or invalid connection blocks the workflow. A dispatched journal write
|
|
101
|
+
whose acknowledgement is lost returns `journal_rpc_outcome_indeterminate`.
|
|
102
|
+
Reconnect to the same journal and inspect or reconcile the **same operation
|
|
103
|
+
identity** before further browser actions. Never mint a new operation ID to
|
|
104
|
+
retry an uncertain Send. The client does not retry transport writes.
|
|
105
|
+
|
|
106
|
+
Transactional Chat Power selection observes the closed composer's effort,
|
|
107
|
+
records one action intent, and then searches the owned slider one step at a
|
|
108
|
+
time within a finite budget. An absent choice is restored only while every
|
|
109
|
+
step and the original position remain proven in the same execution. An
|
|
110
|
+
interrupted action is reconciled by observation; it never restarts probing or
|
|
111
|
+
reconstructs a setting from a digest.
|
|
112
|
+
|
|
113
|
+
## Qualification and Python
|
|
114
|
+
|
|
115
|
+
The optional `transactional-submit-once` smoke uses the production client. Set
|
|
116
|
+
`CHATGPT_E2E_TRANSACTIONAL=1` and `CHATGPT_E2E_JOURNAL_DESCRIPTOR` in its explicit
|
|
117
|
+
context environment to select this service. It checks the exact response,
|
|
118
|
+
receipt and browser turn counts before a same-ID replay.
|
|
119
|
+
|
|
120
|
+
Fresh-client restart qualification also exercised a real completed ChatGPT
|
|
121
|
+
exchange against an isolated authenticated journal prefix ending at the atomic
|
|
122
|
+
`action_prepared` Send event: phase `ready`, target `new_pending`, persisted
|
|
123
|
+
baseline, and no submission witness. After restarting the normal Node journal
|
|
124
|
+
service, a fresh browser client recovered the exact 26-byte synthetic response,
|
|
125
|
+
retained the same Send action, and preserved one user/one assistant turn before
|
|
126
|
+
and after recovery and completed replay. This tests loss of post-Send
|
|
127
|
+
bookkeeping against a real browser result; it does not claim an OS process was
|
|
128
|
+
killed at every individual timing boundary.
|
|
129
|
+
|
|
130
|
+
Python continues to use the versioned Node backend protocol. Configure
|
|
131
|
+
`BackendSession({ agent, operations: { journalService: { descriptorPath } } })`
|
|
132
|
+
in the active browser host; Python handles, operation results and wire schemas
|
|
133
|
+
are unchanged. Python cannot acquire the browser bridge by spawning a plain
|
|
134
|
+
Node subprocess. A bridge-hosted backend transport is still required, and the
|
|
135
|
+
HTTP test relay is usable only where that host permits local sockets.
|
|
136
|
+
|
|
137
|
+
The async journal authority and async DOM factory helpers are TypeScript host
|
|
138
|
+
integration APIs, not new Python wire methods. Owner: SDK runtime maintainers.
|
|
139
|
+
Revisit this intentional language asymmetry if Python gains its own browser
|
|
140
|
+
backend; require existing shared fixtures and conformance before adding one.
|
|
@@ -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
|