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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +43 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +40 -1
  4. package/contracts/v1/fixtures/backend-compatibility.json +21 -0
  5. package/contracts/v1/fixtures/backend-version.json +6 -1
  6. package/contracts/v1/fixtures/download-blocked-by-browser.json +22 -0
  7. package/contracts/v1/fixtures/download-receipt-failed.json +22 -0
  8. package/contracts/v1/fixtures/download-receipt-timeout.json +22 -0
  9. package/contracts/v1/fixtures/journal-rpc-indeterminate.json +32 -0
  10. package/contracts/v1/fixtures/journal-rpc-unsupported-platform.json +32 -0
  11. package/contracts/v1/fixtures/journal-runtime-unavailable.json +32 -0
  12. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  13. package/contracts/v1/fixtures/operation-action.json +13 -0
  14. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  15. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  16. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  17. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  18. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  19. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  20. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  21. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  22. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  23. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  24. package/contracts/v1/fixtures/operation-event.json +18 -0
  25. package/contracts/v1/fixtures/operation-handle.json +10 -0
  26. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  27. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  28. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  29. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  30. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  31. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  32. package/contracts/v1/fixtures/operation-request.json +33 -0
  33. package/contracts/v1/fixtures/operation-state.json +144 -0
  34. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  35. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  36. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  37. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  38. package/contracts/v1/manifest.json +185 -1
  39. package/contracts/v1/parity-suite.json +106 -8
  40. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  41. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  42. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  43. package/contracts/v1/schemas/manifest.schema.json +57 -0
  44. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  45. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  46. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  47. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  48. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  49. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  50. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  51. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  52. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  53. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  54. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  55. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  56. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  57. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  58. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  59. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  60. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  61. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  62. package/contracts/v1/surface-drift-policy.json +50 -0
  63. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  64. package/dist/codex-chatgpt-control-backend.mjs +45882 -9361
  65. package/dist/codex-chatgpt-control-journal.mjs +5547 -0
  66. package/dist/codex-chatgpt-control-live-smoke.bundle.mjs +51677 -0
  67. package/dist/codex-chatgpt-control-release-canary.bundle.mjs +52104 -0
  68. package/dist/codex-chatgpt-control.bundle.mjs +48392 -10260
  69. package/dist/src/backend/client.d.ts +103 -2
  70. package/dist/src/backend/client.js +1436 -97
  71. package/dist/src/backend/compatibility.d.ts +12 -0
  72. package/dist/src/backend/compatibility.js +208 -0
  73. package/dist/src/backend/protocol.d.ts +84 -1
  74. package/dist/src/backend/protocol.js +31 -5
  75. package/dist/src/backend/runtime-identity.d.ts +10 -0
  76. package/dist/src/backend/runtime-identity.js +141 -0
  77. package/dist/src/backend/session.d.ts +10 -2
  78. package/dist/src/backend/session.js +478 -7
  79. package/dist/src/backend/stdio-server.d.ts +4 -1
  80. package/dist/src/backend/stdio-server.js +240 -28
  81. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  82. package/dist/src/browser/active-composer-file-input.js +33 -0
  83. package/dist/src/browser/attach.d.ts +4 -1
  84. package/dist/src/browser/attach.js +136 -28
  85. package/dist/src/browser/chatgpt-url.d.ts +2 -0
  86. package/dist/src/browser/chatgpt-url.js +9 -0
  87. package/dist/src/browser/downloads.d.ts +17 -1
  88. package/dist/src/browser/downloads.js +150 -28
  89. package/dist/src/browser/page-state.js +6 -0
  90. package/dist/src/client.d.ts +43 -0
  91. package/dist/src/client.js +1279 -59
  92. package/dist/src/commands/artifacts.js +6 -4
  93. package/dist/src/commands/chat-popover.d.ts +37 -0
  94. package/dist/src/commands/chat-popover.js +537 -0
  95. package/dist/src/commands/configuration.d.ts +5 -0
  96. package/dist/src/commands/configuration.js +134 -21
  97. package/dist/src/commands/context.js +10 -3
  98. package/dist/src/commands/doctor.d.ts +1 -1
  99. package/dist/src/commands/doctor.js +27 -4
  100. package/dist/src/commands/experience.d.ts +5 -0
  101. package/dist/src/commands/experience.js +104 -21
  102. package/dist/src/commands/files.js +14 -33
  103. package/dist/src/commands/modes.js +85 -21
  104. package/dist/src/commands/power-discovery.d.ts +130 -0
  105. package/dist/src/commands/power-discovery.js +669 -0
  106. package/dist/src/commands/project-sources.js +325 -31
  107. package/dist/src/commands/sequence.js +59 -32
  108. package/dist/src/commands/session.js +19 -6
  109. package/dist/src/commands/work.js +31 -0
  110. package/dist/src/dom/composer-text.d.ts +6 -0
  111. package/dist/src/dom/composer-text.js +85 -0
  112. package/dist/src/errors.d.ts +9 -0
  113. package/dist/src/errors.js +22 -0
  114. package/dist/src/index.d.ts +6 -0
  115. package/dist/src/index.js +9 -0
  116. package/dist/src/operations/artifact-output.d.ts +69 -0
  117. package/dist/src/operations/artifact-output.js +1748 -0
  118. package/dist/src/operations/artifact-stream.d.ts +19 -0
  119. package/dist/src/operations/artifact-stream.js +35 -0
  120. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  121. package/dist/src/operations/artifact-transfer.js +1312 -0
  122. package/dist/src/operations/browser-adapter.d.ts +167 -0
  123. package/dist/src/operations/browser-adapter.js +2103 -0
  124. package/dist/src/operations/browser-observation.d.ts +110 -0
  125. package/dist/src/operations/browser-observation.js +1256 -0
  126. package/dist/src/operations/browser-target.d.ts +105 -0
  127. package/dist/src/operations/browser-target.js +531 -0
  128. package/dist/src/operations/canonical.d.ts +4 -0
  129. package/dist/src/operations/canonical.js +412 -0
  130. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  131. package/dist/src/operations/chatgpt-runtime.js +1232 -0
  132. package/dist/src/operations/client.d.ts +138 -0
  133. package/dist/src/operations/client.js +1145 -0
  134. package/dist/src/operations/collector.d.ts +195 -0
  135. package/dist/src/operations/collector.js +1039 -0
  136. package/dist/src/operations/configuration-routing.d.ts +4 -0
  137. package/dist/src/operations/configuration-routing.js +14 -0
  138. package/dist/src/operations/control.d.ts +372 -0
  139. package/dist/src/operations/control.js +1498 -0
  140. package/dist/src/operations/file-identity.d.ts +33 -0
  141. package/dist/src/operations/file-identity.js +153 -0
  142. package/dist/src/operations/handle.d.ts +19 -0
  143. package/dist/src/operations/handle.js +727 -0
  144. package/dist/src/operations/index.d.ts +25 -0
  145. package/dist/src/operations/index.js +25 -0
  146. package/dist/src/operations/journal-authority.d.ts +12 -0
  147. package/dist/src/operations/journal-authority.js +1 -0
  148. package/dist/src/operations/journal-rpc-client.d.ts +12 -0
  149. package/dist/src/operations/journal-rpc-client.js +225 -0
  150. package/dist/src/operations/journal-rpc-protocol.d.ts +32 -0
  151. package/dist/src/operations/journal-rpc-protocol.js +214 -0
  152. package/dist/src/operations/journal-rpc-server.d.ts +15 -0
  153. package/dist/src/operations/journal-rpc-server.js +272 -0
  154. package/dist/src/operations/journal.d.ts +143 -0
  155. package/dist/src/operations/journal.js +1957 -0
  156. package/dist/src/operations/production-attachments.d.ts +118 -0
  157. package/dist/src/operations/production-attachments.js +1035 -0
  158. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  159. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  160. package/dist/src/operations/production-chatgpt-attachments.d.ts +80 -0
  161. package/dist/src/operations/production-chatgpt-attachments.js +1639 -0
  162. package/dist/src/operations/production-configuration.d.ts +42 -0
  163. package/dist/src/operations/production-configuration.js +1544 -0
  164. package/dist/src/operations/production-primitives.d.ts +53 -0
  165. package/dist/src/operations/production-primitives.js +856 -0
  166. package/dist/src/operations/production-work-steer.d.ts +203 -0
  167. package/dist/src/operations/production-work-steer.js +1218 -0
  168. package/dist/src/operations/recovery.d.ts +80 -0
  169. package/dist/src/operations/recovery.js +174 -0
  170. package/dist/src/operations/runtime-adapter.d.ts +126 -0
  171. package/dist/src/operations/runtime-adapter.js +802 -0
  172. package/dist/src/operations/send-once.d.ts +224 -0
  173. package/dist/src/operations/send-once.js +1081 -0
  174. package/dist/src/operations/service.d.ts +286 -0
  175. package/dist/src/operations/service.js +2831 -0
  176. package/dist/src/operations/staging.d.ts +136 -0
  177. package/dist/src/operations/staging.js +630 -0
  178. package/dist/src/operations/state-machine.d.ts +29 -0
  179. package/dist/src/operations/state-machine.js +2047 -0
  180. package/dist/src/operations/submission.d.ts +423 -0
  181. package/dist/src/operations/submission.js +1676 -0
  182. package/dist/src/operations/transactional-chat-power.d.ts +17 -0
  183. package/dist/src/operations/transactional-chat-power.js +212 -0
  184. package/dist/src/operations/turn-ownership.d.ts +202 -0
  185. package/dist/src/operations/turn-ownership.js +700 -0
  186. package/dist/src/operations/types.d.ts +454 -0
  187. package/dist/src/operations/types.js +20 -0
  188. package/dist/src/operations/wire-requests.d.ts +21 -0
  189. package/dist/src/operations/wire-requests.js +396 -0
  190. package/dist/src/operations/wire-results.d.ts +97 -0
  191. package/dist/src/operations/wire-results.js +818 -0
  192. package/dist/src/runner/responses.d.ts +3 -1
  193. package/dist/src/runner/responses.js +19 -1
  194. package/dist/src/runner/result.js +227 -55
  195. package/dist/src/runner/types.d.ts +12 -0
  196. package/dist/src/runtime/command-routing.d.ts +149 -0
  197. package/dist/src/runtime/command-routing.js +431 -0
  198. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  199. package/dist/src/runtime/coordinated-browser.js +315 -0
  200. package/dist/src/runtime/coordinated-page.d.ts +41 -0
  201. package/dist/src/runtime/coordinated-page.js +651 -0
  202. package/dist/src/runtime/operation-context.d.ts +142 -0
  203. package/dist/src/runtime/operation-context.js +410 -0
  204. package/dist/src/runtime/runtime-session.d.ts +95 -0
  205. package/dist/src/runtime/runtime-session.js +314 -0
  206. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  207. package/dist/src/runtime/tab-coordinator.js +1200 -0
  208. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  209. package/dist/src/runtime/value-boundaries.js +49 -0
  210. package/dist/src/safety/blockers.js +8 -1
  211. package/dist/src/safety/untrusted-output.js +2 -1
  212. package/dist/src/scripts/backend-server.js +4 -1
  213. package/dist/src/scripts/capture-surface-profile.js +7 -2
  214. package/dist/src/scripts/journal-server.d.ts +2 -0
  215. package/dist/src/scripts/journal-server.js +37 -0
  216. package/dist/src/scripts/live-smoke/harness.js +79 -7
  217. package/dist/src/scripts/live-smoke/scenarios.d.ts +42 -1
  218. package/dist/src/scripts/live-smoke/scenarios.js +147 -30
  219. package/dist/src/scripts/live-smoke/transactional.d.ts +17 -0
  220. package/dist/src/scripts/live-smoke/transactional.js +254 -0
  221. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  222. package/dist/src/scripts/release-canary-module.js +17 -4
  223. package/dist/src/types.d.ts +38 -1
  224. package/package.json +8 -3
  225. package/references/2026-08-16-transactional-operations.md +459 -0
  226. package/references/2026-09-06-journal-service.md +140 -0
  227. package/references/agents-runner.md +34 -0
  228. package/references/backend-protocol.md +176 -0
  229. package/references/python-parity.md +137 -0
  230. package/references/responses-adapter.md +19 -0
  231. package/references/streaming.md +6 -0
@@ -0,0 +1,1145 @@
1
+ import { fingerprintOperationFile, revalidateOperationFile } from "./file-identity.js";
2
+ import { assertDurableCapturePolicyShape } from "./state-machine.js";
3
+ /**
4
+ * The public operations facade is deliberately a thin composition layer.
5
+ *
6
+ * `OperationService` owns request identity, journal state, and non-repeatable
7
+ * action idempotency. This class owns only the local-file boundary: it
8
+ * snapshots the caller request, hashes local files in request order, and
9
+ * supplies a browser adapter whose one file-handoff callback revalidates the
10
+ * same file identities immediately before handing them to the provider.
11
+ *
12
+ * The browser adapter factory is optional. A direct adapter is useful for
13
+ * callers that already created a request-scoped closure. The factory exists
14
+ * for the normal SDK path, where prompt/configuration/path values should stay
15
+ * in an ephemeral adapter closure rather than crossing the service boundary.
16
+ */
17
+ const SAFE_INPUT_PATH_PREFIX = "operation-input-";
18
+ const SAFE_OUTPUT_PATH = "operation-output";
19
+ /** Keep ephemeral raw-path closures bounded even when callers never collect. */
20
+ const DEFAULT_MAX_CACHED_ADAPTERS = 8;
21
+ const MAX_CACHED_ADAPTERS = 256;
22
+ export class OperationClientError extends Error {
23
+ code;
24
+ constructor(code, message) {
25
+ super(message);
26
+ this.code = code;
27
+ this.name = "OperationClientError";
28
+ }
29
+ }
30
+ /**
31
+ * Additive TypeScript operations API. The supplied adapter is also used for
32
+ * browser-free completion paths because the service requires a uniform port;
33
+ * `inspect` remains entirely browser-free. A request-scoped factory may
34
+ * replace the base adapter for submit/run and is cached in memory only for
35
+ * subsequent collect/control calls in the same process.
36
+ */
37
+ export class OperationClient {
38
+ service;
39
+ adapter;
40
+ fingerprint;
41
+ revalidate;
42
+ adapterFactory;
43
+ handleAdapterFactory;
44
+ controlAdapterFactory;
45
+ maxCachedAdapters;
46
+ requestAdapters = new Map();
47
+ constructor(service, adapter, options = {}) {
48
+ this.service = service;
49
+ this.adapter = adapter;
50
+ this.fingerprint = options.fingerprint ?? fingerprintOperationFile;
51
+ this.revalidate = options.revalidate ?? revalidateOperationFile;
52
+ this.adapterFactory = options.adapterFactory;
53
+ this.handleAdapterFactory = options.handleAdapterFactory;
54
+ this.controlAdapterFactory = options.controlAdapterFactory;
55
+ this.maxCachedAdapters = validateMaxCachedAdapters(options.maxCachedAdapters);
56
+ }
57
+ /** Fingerprint inputs, then execute the service's one-submit protocol. */
58
+ async submit(request, options = {}) {
59
+ const prepared = await this.prepareSubmit(request, options.signal);
60
+ const adapter = await this.adapterForSubmit(prepared);
61
+ const result = await this.service.submit(prepared.serviceRequest, prepared.manifest, adapter, forwardSubmitOptions(options, prepared.signal));
62
+ if (isTerminalSubmitResult(result)) {
63
+ this.forgetAdapter(result.handle);
64
+ }
65
+ else {
66
+ this.rememberAdapter(result.handle, adapter);
67
+ }
68
+ return freshResult(result);
69
+ }
70
+ /** Collect only the exact operation-owned turn; never composes or submits. */
71
+ async collect(handle, options = {}) {
72
+ const snapshot = cloneFrozen(handle, "invalid_operation_handle");
73
+ const adapter = await this.adapterForHandle(snapshot, options.signal);
74
+ const result = await this.service.collect(snapshot, adapter, forwardCollectorOptions(options));
75
+ if (result.kind === "completed")
76
+ this.forgetAdapter(snapshot);
77
+ return freshResult(result);
78
+ }
79
+ /** Inspect durable state without touching the browser. */
80
+ async inspect(handle) {
81
+ const snapshot = cloneFrozen(handle, "invalid_operation_handle");
82
+ return freshResult(await this.service.inspect(snapshot));
83
+ }
84
+ /** Apply one operation-bound Stop or Work steer. */
85
+ async control(request, options = {}) {
86
+ const snapshot = cloneFrozen(request, "invalid_operation_control_request");
87
+ // Authenticate exactly once before selecting an adapter. A control
88
+ // factory receives the same durable target/state snapshot used below;
89
+ // invoking it before inspection would permit a stale target binding and
90
+ // invoking adapterForHandle first would perform a second inspect and/or
91
+ // accidentally reuse a submit adapter that cannot carry steer prompt
92
+ // state.
93
+ const reconstruction = await this.reconstructionForHandle(snapshot.parent);
94
+ const adapter = this.controlAdapterFactory === undefined
95
+ ? await this.adapterForAuthenticatedHandle(snapshot.parent, options.signal, reconstruction)
96
+ : await this.adapterForControl(snapshot, options.signal, reconstruction);
97
+ return freshResult(await this.service.control(snapshot, adapter, forwardControlOptions(options)));
98
+ }
99
+ /** SDK-only composition of one submit followed by one collect. */
100
+ async run(request, options = {}) {
101
+ const prepared = await this.prepareSubmit(request, options.signal);
102
+ const adapter = await this.adapterForSubmit(prepared);
103
+ const result = await this.service.run(prepared.serviceRequest, prepared.manifest, adapter, forwardRunOptions(options, prepared.signal));
104
+ if (isTerminalRunResult(result)) {
105
+ this.forgetAdapter(result.submit.handle);
106
+ }
107
+ else {
108
+ this.rememberAdapter(result.submit.handle, adapter);
109
+ }
110
+ return freshResult(result);
111
+ }
112
+ async prepareSubmit(request, requestedSignal) {
113
+ const snapshot = cloneFrozen(request, "invalid_operation_request");
114
+ const signal = requestedSignal ?? new AbortController().signal;
115
+ assertAbortSignal(signal);
116
+ const requestedFiles = readRequestedFiles(snapshot);
117
+ const identities = [];
118
+ for (const file of requestedFiles) {
119
+ try {
120
+ const identity = await this.fingerprint(file.path, file.displayName, { signal });
121
+ identities.push(freezeIdentity(identity));
122
+ }
123
+ catch (error) {
124
+ throw fileBoundaryError(error);
125
+ }
126
+ }
127
+ const manifest = Object.freeze(identities.map(identity => identity.manifest));
128
+ const serviceRequest = sanitizeServiceRequest(snapshot, identities);
129
+ return Object.freeze({
130
+ request: snapshot,
131
+ serviceRequest,
132
+ identities: Object.freeze(identities),
133
+ manifest,
134
+ signal
135
+ });
136
+ }
137
+ async adapterForSubmit(prepared) {
138
+ // A non-terminal submit result deliberately retains its request-scoped
139
+ // adapter so an identical same-operation retry can reconcile the durable
140
+ // Send boundary observation-only. The client cannot recompute the
141
+ // journal's keyed request digest, so select by the already validated
142
+ // operation ID. OperationService authenticates the immutable request
143
+ // digest before it invokes any adapter method; a changed same-ID request
144
+ // therefore still fails browser-free with operation_request_mismatch.
145
+ const cached = this.cachedAdapterForOperation(prepared.request.operationId);
146
+ if (cached !== undefined)
147
+ return cached;
148
+ let adapter = this.adapter;
149
+ if (this.adapterFactory !== undefined) {
150
+ try {
151
+ adapter = await this.adapterFactory(Object.freeze({
152
+ request: prepared.request,
153
+ files: prepared.identities,
154
+ signal: prepared.signal
155
+ }));
156
+ }
157
+ catch {
158
+ // Let OperationService create the durable request record before the
159
+ // read-only target probe reports this blocker. Throwing here would
160
+ // lose the operation handle and make a same-ID recovery opaque.
161
+ adapter = unavailableAdapter("adapter_unavailable");
162
+ }
163
+ }
164
+ try {
165
+ return this.guardAdapter(adapter, prepared.identities, prepared.signal);
166
+ }
167
+ catch {
168
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter is incomplete.");
169
+ }
170
+ }
171
+ guardAdapter(adapter, identities, signal) {
172
+ if (adapter === null || typeof adapter !== "object") {
173
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter is incomplete.");
174
+ }
175
+ // Factory output is untrusted at this boundary. Read only own data
176
+ // descriptors so an accessor (or a proxy get trap) cannot run while the
177
+ // facade is merely validating the adapter. Capture every method before
178
+ // building wrappers so a later mutation/accessor replacement cannot alter
179
+ // the validated surface.
180
+ const resolveTarget = requiredMethod(adapter, "resolveTarget");
181
+ const submissionInput = requiredAdapterObject(adapter, "submission");
182
+ const observeStaging = requiredMethod(submissionInput, "observeStaging");
183
+ const executeFileHandoffOnce = requiredMethod(submissionInput, "executeFileHandoffOnce");
184
+ const observeAttachments = requiredMethod(submissionInput, "observeAttachments");
185
+ const prepareSend = requiredMethod(submissionInput, "prepareSend");
186
+ const executePreparedSend = requiredMethod(submissionInput, "executePreparedSend");
187
+ const verifyPreparedSend = requiredMethod(submissionInput, "verifyPreparedSend");
188
+ const recoverSend = requiredMethod(submissionInput, "recoverSend");
189
+ const recoverAuthenticatedSend = optionalMethod(submissionInput, "recoverAuthenticatedSend");
190
+ const executeFinalTabTransaction = requiredMethod(submissionInput, "executeFinalTabTransaction");
191
+ const collectorInput = requiredAdapterObject(adapter, "collector");
192
+ const readContext = requiredMethod(collectorInput, "readContext");
193
+ const observe = requiredMethod(collectorInput, "observe");
194
+ const sleep = requiredMethod(collectorInput, "sleep");
195
+ const submission = Object.freeze({
196
+ observeStaging: (request) => observeStaging(request),
197
+ executeFileHandoffOnce: async (request) => {
198
+ for (const identity of identities) {
199
+ try {
200
+ await this.revalidate(identity, { signal });
201
+ }
202
+ catch (error) {
203
+ throw fileBoundaryError(error);
204
+ }
205
+ }
206
+ return executeFileHandoffOnce(request);
207
+ },
208
+ observeAttachments: (request) => observeAttachments(request),
209
+ prepareSend: (request) => prepareSend(request),
210
+ executePreparedSend: (request) => executePreparedSend(request),
211
+ verifyPreparedSend: (request) => verifyPreparedSend(request),
212
+ recoverSend: (request) => recoverSend(request),
213
+ ...(recoverAuthenticatedSend === undefined ? {} : { recoverAuthenticatedSend }),
214
+ executeFinalTabTransaction: (request) => executeFinalTabTransaction(request)
215
+ });
216
+ const collector = Object.freeze({
217
+ readContext: (request) => readContext(request),
218
+ observe: (request) => observe(request),
219
+ sleep: (milliseconds, sleepSignal) => sleep(milliseconds, sleepSignal)
220
+ });
221
+ let control;
222
+ const controlInput = optionalDataProperty(adapter, "control");
223
+ if (controlInput !== undefined) {
224
+ const controlObject = adapterObject(controlInput);
225
+ const observeTurn = requiredMethod(controlObject, "observeTurn");
226
+ const executeOnce = requiredMethod(controlObject, "executeOnce");
227
+ const observePostcondition = requiredMethod(controlObject, "observePostcondition");
228
+ const postconditionRetryInput = optionalDataProperty(controlObject, "postconditionRetry");
229
+ const postconditionRetry = postconditionRetryInput === undefined
230
+ ? undefined
231
+ : (() => {
232
+ const policy = adapterObject(postconditionRetryInput);
233
+ const maxAttempts = optionalDataProperty(policy, "maxAttempts");
234
+ const intervalMs = optionalDataProperty(policy, "intervalMs");
235
+ if (!Number.isSafeInteger(maxAttempts) || !Number.isSafeInteger(intervalMs)) {
236
+ throw new OperationClientError("adapter_unavailable", "The operation control adapter has an invalid postcondition retry policy.");
237
+ }
238
+ return Object.freeze({
239
+ maxAttempts: maxAttempts,
240
+ intervalMs: intervalMs
241
+ });
242
+ })();
243
+ // Work-steer is an optional four-phase capability. Existing Stop-only
244
+ // adapters remain valid, but a partially supplied phase surface is not
245
+ // safe: allowing it through would defer an adapter contract failure
246
+ // until after a durable control intent exists.
247
+ const prepareSteer = optionalMethod(controlObject, "prepareSteer");
248
+ const executeSteerPrepared = optionalMethod(controlObject, "executeSteerPrepared");
249
+ const verifySteer = optionalMethod(controlObject, "verifySteer");
250
+ const recoverSteer = optionalMethod(controlObject, "recoverSteer");
251
+ const steerPhaseMethods = [prepareSteer, executeSteerPrepared, verifySteer, recoverSteer];
252
+ const steerPhaseCount = steerPhaseMethods.filter(method => method !== undefined).length;
253
+ if (steerPhaseCount !== 0 && steerPhaseCount !== steerPhaseMethods.length) {
254
+ throw new OperationClientError("adapter_unavailable", "The operation control adapter has incomplete Work-steer phases.");
255
+ }
256
+ const guardedControl = {
257
+ observeTurn: (request) => observeTurn(request),
258
+ executeOnce: (request) => executeOnce(request),
259
+ observePostcondition: (request) => observePostcondition(request)
260
+ };
261
+ if (postconditionRetry !== undefined)
262
+ guardedControl.postconditionRetry = postconditionRetry;
263
+ if (prepareSteer !== undefined && executeSteerPrepared !== undefined && verifySteer !== undefined && recoverSteer !== undefined) {
264
+ guardedControl.prepareSteer = (request) => prepareSteer(request);
265
+ guardedControl.executeSteerPrepared = (request) => executeSteerPrepared(request);
266
+ guardedControl.verifySteer = (request) => verifySteer(request);
267
+ guardedControl.recoverSteer = (request) => recoverSteer(request);
268
+ }
269
+ control = Object.freeze(guardedControl);
270
+ }
271
+ let artifacts;
272
+ const artifactsInput = optionalDataProperty(adapter, "artifacts");
273
+ if (artifactsInput !== undefined) {
274
+ const artifactsObject = adapterObject(artifactsInput);
275
+ const transfer = requiredMethod(artifactsObject, "transfer");
276
+ artifacts = Object.freeze({
277
+ transfer: (request) => transfer(request)
278
+ });
279
+ }
280
+ let staging;
281
+ const stagingInput = optionalDataProperty(adapter, "staging");
282
+ if (stagingInput !== undefined) {
283
+ const stagingObject = adapterObject(stagingInput);
284
+ const readCurrent = requiredMethod(stagingObject, "readCurrent");
285
+ const mutateOnce = requiredMethod(stagingObject, "mutateOnce");
286
+ const observeStagingState = requiredMethod(stagingObject, "observe");
287
+ staging = Object.freeze({
288
+ readCurrent: (request) => readCurrent(request),
289
+ mutateOnce: (request) => mutateOnce(request),
290
+ observe: (request) => observeStagingState(request)
291
+ });
292
+ }
293
+ const guarded = {
294
+ resolveTarget: (request) => resolveTarget(request),
295
+ submission,
296
+ collector,
297
+ ...(artifacts === undefined ? {} : { artifacts }),
298
+ ...(staging === undefined ? {} : { staging }),
299
+ ...(control === undefined ? {} : { control })
300
+ };
301
+ return Object.freeze(guarded);
302
+ }
303
+ async adapterForHandle(handle, requestedSignal) {
304
+ const reconstruction = await this.reconstructionForHandle(handle);
305
+ return await this.adapterForAuthenticatedHandle(handle, requestedSignal, reconstruction);
306
+ }
307
+ /**
308
+ * Authenticate a handle and project only the immutable target context
309
+ * needed by a request-local adapter. The inspect result is intentionally
310
+ * consumed once and passed to adapter selection; callers that need a
311
+ * prompt-bearing control closure must not repeat this read.
312
+ */
313
+ async reconstructionForHandle(handle) {
314
+ // Always authenticate the locator before consulting the in-process cache.
315
+ // Otherwise a caller could mutate revision/phase on a still-cacheable
316
+ // identity and reuse a target-bound adapter without a fresh journal read.
317
+ const inspected = await this.service.inspect(handle);
318
+ try {
319
+ return reconstructionContext(inspected, handle);
320
+ }
321
+ catch (error) {
322
+ // A pre-target record cannot supply a target-bound reconstruction
323
+ // context. Do not invoke a factory with a type-unsound partial context;
324
+ // OperationService remains authoritative and rejects the missing target
325
+ // before browser use.
326
+ if (error instanceof OperationClientError && error.code === "target_binding_missing") {
327
+ return undefined;
328
+ }
329
+ throw error;
330
+ }
331
+ }
332
+ async adapterForAuthenticatedHandle(handle, requestedSignal, reconstruction) {
333
+ if (reconstruction === undefined)
334
+ return this.adapter;
335
+ if (reconstruction.target.targetLifecycle === "new_pending") {
336
+ throw new OperationClientError("new_target_not_established", "A pending new target cannot be reconstructed for collection or control.");
337
+ }
338
+ if (reconstruction.state.phase === "completed") {
339
+ // OperationService returns completed receipts without touching its
340
+ // adapter. Keep that browser-free guarantee after a process restart too.
341
+ return this.adapter;
342
+ }
343
+ const factoryContext = reconstruction.context;
344
+ const key = adapterKey(handle.operationId, handle.requestDigest);
345
+ const cached = this.requestAdapters.get(key);
346
+ if (cached !== undefined) {
347
+ // Map insertion order is the LRU order. A read promotes this entry.
348
+ this.requestAdapters.delete(key);
349
+ this.requestAdapters.set(key, cached);
350
+ return cached;
351
+ }
352
+ if (this.handleAdapterFactory === undefined)
353
+ return this.adapter;
354
+ let recreated;
355
+ try {
356
+ recreated = await this.handleAdapterFactory(factoryContext);
357
+ }
358
+ catch {
359
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter could not be recreated from the operation handle.");
360
+ }
361
+ const signal = requestedSignal ?? new AbortController().signal;
362
+ assertAbortSignal(signal);
363
+ let guarded;
364
+ try {
365
+ guarded = this.guardAdapter(recreated, [], signal);
366
+ }
367
+ catch {
368
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter could not be recreated from the operation handle.");
369
+ }
370
+ this.rememberAdapter(factoryContext.handle, guarded);
371
+ return guarded;
372
+ }
373
+ async adapterForControl(request, requestedSignal, reconstruction) {
374
+ // Completed records and pre-target records remain browser-free/legacy
375
+ // compatible. There is no safe target-bound context with which to invoke
376
+ // a control factory in either case, and OperationService owns the final
377
+ // blocker/receipt decision.
378
+ if (reconstruction === undefined || reconstruction.state.phase === "completed") {
379
+ return await this.adapterForAuthenticatedHandle(request.parent, requestedSignal, reconstruction);
380
+ }
381
+ if (reconstruction.target.targetLifecycle === "new_pending") {
382
+ throw new OperationClientError("new_target_not_established", "A pending new target cannot be reconstructed for collection or control.");
383
+ }
384
+ const signal = requestedSignal ?? new AbortController().signal;
385
+ assertAbortSignal(signal);
386
+ let created;
387
+ try {
388
+ created = await this.controlAdapterFactory(makeControlFactoryContext(request, reconstruction));
389
+ }
390
+ catch {
391
+ // The control factory is the only component that can hold a raw steer
392
+ // prompt and a provider/browser closure. If it fails, do not fall back
393
+ // to a cached submit adapter: that would either lose the prompt or
394
+ // mutate an unintended target. Keep this message static and redacted.
395
+ throw new OperationClientError("adapter_unavailable", "The operation control browser adapter is unavailable.");
396
+ }
397
+ try {
398
+ // Control adapters are deliberately not remembered in requestAdapters.
399
+ // Their closure may contain a steer prompt and must die with this call.
400
+ return this.guardAdapter(created, [], signal);
401
+ }
402
+ catch {
403
+ throw new OperationClientError("adapter_unavailable", "The operation control browser adapter is incomplete.");
404
+ }
405
+ }
406
+ rememberAdapter(handle, adapter) {
407
+ const key = adapterKey(handle.operationId, handle.requestDigest);
408
+ this.requestAdapters.delete(key);
409
+ this.requestAdapters.set(key, adapter);
410
+ while (this.requestAdapters.size > this.maxCachedAdapters) {
411
+ const oldest = this.requestAdapters.keys().next().value;
412
+ if (oldest === undefined)
413
+ break;
414
+ this.requestAdapters.delete(oldest);
415
+ }
416
+ }
417
+ cachedAdapterForOperation(operationId) {
418
+ const prefix = `${operationId}\0`;
419
+ let matchedKey;
420
+ let matchedAdapter;
421
+ for (const [key, adapter] of this.requestAdapters) {
422
+ if (key.startsWith(prefix)) {
423
+ matchedKey = key;
424
+ matchedAdapter = adapter;
425
+ }
426
+ }
427
+ if (matchedKey === undefined || matchedAdapter === undefined)
428
+ return undefined;
429
+ // Preserve the cache's LRU invariant when a submit retry hits it.
430
+ this.requestAdapters.delete(matchedKey);
431
+ this.requestAdapters.set(matchedKey, matchedAdapter);
432
+ return matchedAdapter;
433
+ }
434
+ forgetAdapter(handle) {
435
+ this.requestAdapters.delete(adapterKey(handle.operationId, handle.requestDigest));
436
+ }
437
+ }
438
+ /** Alias kept for callers that prefer the plural namespace terminology. */
439
+ export const OperationsClient = OperationClient;
440
+ export function createOperationClient(service, adapter, options = {}) {
441
+ return new OperationClient(service, adapter, options);
442
+ }
443
+ /**
444
+ * Project one authenticated inspect result into the restart factory surface.
445
+ * Only own data descriptors are read. This is deliberately verbose: using a
446
+ * spread, structuredClone, or direct property access here would execute a
447
+ * hostile getter before the adapter factory has even been called.
448
+ */
449
+ function reconstructionContext(inspected, requestedHandle) {
450
+ const inspectedRecord = requiredObject(inspected, "inspect result");
451
+ const freshHandle = normalizeHandle(requiredData(inspectedRecord, "handle"), requestedHandle);
452
+ const rawState = requiredData(inspectedRecord, "state");
453
+ const stateRecord = requiredObject(rawState, "durable operation state");
454
+ const state = normalizeDurableState(stateRecord, freshHandle);
455
+ const target = state.target;
456
+ const context = makeFactoryContext(freshHandle, state, target);
457
+ return Object.freeze({ context, state, target });
458
+ }
459
+ function normalizeHandle(value, requested) {
460
+ const record = requiredObject(value, "operation handle");
461
+ const schemaVersion = requiredString(record, "schemaVersion");
462
+ const operationId = requiredString(record, "operationId");
463
+ const requestDigest = requiredString(record, "requestDigest");
464
+ const surface = requiredString(record, "surface");
465
+ const revision = requiredSafeInteger(record, "revision");
466
+ const phase = requiredString(record, "phase");
467
+ const mutationBoundary = requiredString(record, "mutationBoundary");
468
+ const targetBindingDigest = optionalString(record, "targetBindingDigest");
469
+ if (schemaVersion !== requested.schemaVersion
470
+ || operationId !== requested.operationId
471
+ || requestDigest !== requested.requestDigest
472
+ || surface !== requested.surface
473
+ || revision < requested.revision
474
+ || !isOperationSurface(surface)
475
+ || !isOperationPhase(phase)
476
+ || !isMutationBoundary(mutationBoundary)
477
+ || (targetBindingDigest !== undefined && !isDigest(targetBindingDigest))
478
+ || (requested.targetBindingDigest !== undefined
479
+ && targetBindingDigest !== requested.targetBindingDigest)
480
+ || (revision === requested.revision
481
+ && (phase !== requested.phase
482
+ || mutationBoundary !== requested.mutationBoundary
483
+ || targetBindingDigest !== requested.targetBindingDigest))) {
484
+ throw new OperationClientError("invalid_operation_handle", "The authenticated operation handle is inconsistent.");
485
+ }
486
+ return Object.freeze({
487
+ schemaVersion: schemaVersion,
488
+ operationId,
489
+ requestDigest,
490
+ surface: surface,
491
+ revision,
492
+ phase: phase,
493
+ mutationBoundary: mutationBoundary,
494
+ ...(targetBindingDigest === undefined ? {} : { targetBindingDigest })
495
+ });
496
+ }
497
+ function normalizeDurableState(value, handle) {
498
+ const schemaVersion = requiredString(value, "schemaVersion");
499
+ const operationId = requiredString(value, "operationId");
500
+ const requestDigest = requiredString(value, "requestDigest");
501
+ const surface = requiredString(value, "surface");
502
+ const phase = requiredString(value, "phase");
503
+ const mutationBoundary = requiredString(value, "mutationBoundary");
504
+ const revision = requiredSafeInteger(value, "revision");
505
+ const capturePolicyValue = optionalDataProperty(value, "capturePolicy");
506
+ if (capturePolicyValue !== undefined)
507
+ assertDurableCapturePolicyShape(capturePolicyValue);
508
+ const targetValue = optionalDataProperty(value, "target");
509
+ if (targetValue === undefined) {
510
+ throw new OperationClientError("target_binding_missing", "The durable operation has no target binding.");
511
+ }
512
+ const target = normalizeTarget(targetValue);
513
+ if (schemaVersion !== "chatgpt.browser_control.operation.v1"
514
+ || operationId !== handle.operationId
515
+ || requestDigest !== handle.requestDigest
516
+ || surface !== handle.surface
517
+ || revision !== handle.revision
518
+ || phase !== handle.phase
519
+ || mutationBoundary !== handle.mutationBoundary
520
+ || handle.targetBindingDigest === undefined
521
+ || (target.targetEstablishment !== undefined
522
+ && target.targetEstablishment.targetBindingDigest !== handle.targetBindingDigest)
523
+ || !isOperationSurface(surface)
524
+ || !isOperationPhase(phase)
525
+ || !isMutationBoundary(mutationBoundary)) {
526
+ throw new OperationClientError("invalid_operation_state", "The authenticated operation state is inconsistent.");
527
+ }
528
+ return Object.freeze({
529
+ schemaVersion: schemaVersion,
530
+ operationId,
531
+ requestDigest,
532
+ surface: surface,
533
+ phase: phase,
534
+ mutationBoundary: mutationBoundary,
535
+ revision,
536
+ target,
537
+ ...(capturePolicyValue === undefined ? {} : { capturePolicy: capturePolicyValue })
538
+ });
539
+ }
540
+ function normalizeTarget(value) {
541
+ const record = requiredObject(value, "durable target binding");
542
+ const providerId = requiredBoundedString(record, "providerId");
543
+ const browserId = requiredBoundedString(record, "browserId");
544
+ const tabId = requiredBoundedString(record, "tabId");
545
+ const coordinationScope = requiredString(record, "coordinationScope");
546
+ const evidenceProfile = normalizeEvidenceProfile(requiredData(record, "evidenceProfile"));
547
+ const targetLifecycle = optionalString(record, "targetLifecycle") ?? "fixed";
548
+ const tabClaimEvidenceDigest = optionalDigest(record, "tabClaimEvidenceDigest");
549
+ const canonicalThreadUrl = optionalBoundedString(record, "canonicalThreadUrl");
550
+ const conversationId = optionalBoundedString(record, "conversationId");
551
+ const userTurnBaselineDigest = optionalDigest(record, "userTurnBaselineDigest");
552
+ const assistantTurnBaselineDigest = optionalDigest(record, "assistantTurnBaselineDigest");
553
+ const configurationReceiptDigest = optionalDigest(record, "configurationReceiptDigest");
554
+ const newTargetAnchorDigest = optionalDigest(record, "newTargetAnchorDigest");
555
+ const blankTaskEvidenceDigest = optionalDigest(record, "blankTaskEvidenceDigest");
556
+ const targetEstablishmentValue = optionalDataProperty(record, "targetEstablishment");
557
+ const targetEstablishment = targetEstablishmentValue === undefined
558
+ ? undefined
559
+ : normalizeTargetEstablishment(targetEstablishmentValue);
560
+ if ((coordinationScope !== "process" && coordinationScope !== "provider")
561
+ || (targetLifecycle !== "fixed" && targetLifecycle !== "new_pending" && targetLifecycle !== "new_established")
562
+ || (coordinationScope === "provider"
563
+ && (tabClaimEvidenceDigest === undefined || evidenceProfile.authoritativeTabClaim !== "required"))) {
564
+ throw new OperationClientError("invalid_operation_state", "The authenticated target binding is invalid.");
565
+ }
566
+ validateTargetLifecycle(targetLifecycle, evidenceProfile, canonicalThreadUrl, conversationId, newTargetAnchorDigest, blankTaskEvidenceDigest, targetEstablishment);
567
+ return Object.freeze({
568
+ providerId,
569
+ browserId,
570
+ tabId,
571
+ coordinationScope,
572
+ ...(tabClaimEvidenceDigest === undefined ? {} : { tabClaimEvidenceDigest }),
573
+ ...(canonicalThreadUrl === undefined ? {} : { canonicalThreadUrl }),
574
+ ...(conversationId === undefined ? {} : { conversationId }),
575
+ ...(userTurnBaselineDigest === undefined ? {} : { userTurnBaselineDigest }),
576
+ ...(assistantTurnBaselineDigest === undefined ? {} : { assistantTurnBaselineDigest }),
577
+ ...(configurationReceiptDigest === undefined ? {} : { configurationReceiptDigest }),
578
+ evidenceProfile,
579
+ ...(targetLifecycle === "fixed" ? {} : { targetLifecycle }),
580
+ ...(newTargetAnchorDigest === undefined ? {} : { newTargetAnchorDigest }),
581
+ ...(blankTaskEvidenceDigest === undefined ? {} : { blankTaskEvidenceDigest }),
582
+ ...(targetEstablishment === undefined ? {} : { targetEstablishment })
583
+ });
584
+ }
585
+ function validateTargetLifecycle(lifecycle, evidenceProfile, canonicalThreadUrl, conversationId, newTargetAnchorDigest, blankTaskEvidenceDigest, targetEstablishment) {
586
+ if (lifecycle === "fixed") {
587
+ if (newTargetAnchorDigest !== undefined || blankTaskEvidenceDigest !== undefined || targetEstablishment !== undefined) {
588
+ throw new OperationClientError("invalid_operation_state", "A fixed target contains new-target establishment fields.");
589
+ }
590
+ return;
591
+ }
592
+ if (newTargetAnchorDigest === undefined || blankTaskEvidenceDigest === undefined) {
593
+ throw new OperationClientError("invalid_operation_state", "A new target is missing its immutable anchor evidence.");
594
+ }
595
+ if (lifecycle === "new_pending") {
596
+ if (canonicalThreadUrl !== undefined || conversationId !== undefined || targetEstablishment !== undefined) {
597
+ throw new OperationClientError("invalid_operation_state", "A pending new target contains provider identity too early.");
598
+ }
599
+ if (evidenceProfile.stableConversationId !== "unavailable" || evidenceProfile.stableUserTurnId !== "unavailable") {
600
+ throw new OperationClientError("invalid_operation_state", "A pending new target advertises provider identity too early.");
601
+ }
602
+ return;
603
+ }
604
+ if (canonicalThreadUrl === undefined
605
+ || conversationId === undefined
606
+ || targetEstablishment === undefined
607
+ || evidenceProfile.stableConversationId !== "required"
608
+ || evidenceProfile.stableUserTurnId !== "required") {
609
+ throw new OperationClientError("invalid_operation_state", "An established new target is missing provider identity evidence.");
610
+ }
611
+ if (targetEstablishment.conversationId !== conversationId
612
+ || targetEstablishment.canonicalThreadUrl !== canonicalThreadUrl
613
+ || targetEstablishment.anchorDigest !== newTargetAnchorDigest) {
614
+ throw new OperationClientError("invalid_operation_state", "New-target establishment identity does not match its durable binding.");
615
+ }
616
+ }
617
+ function normalizeEvidenceProfile(value) {
618
+ const record = requiredObject(value, "target evidence profile");
619
+ const providerIdentity = requiredString(record, "providerIdentity");
620
+ const stableTabId = requiredString(record, "stableTabId");
621
+ const stableConversationId = requiredString(record, "stableConversationId");
622
+ const stableUserTurnId = requiredString(record, "stableUserTurnId");
623
+ const authoritativeTabClaim = requiredString(record, "authoritativeTabClaim");
624
+ const replacementTabRecovery = requiredBoolean(record, "replacementTabRecovery");
625
+ if (!isAvailability(providerIdentity)
626
+ || !isAvailability(stableTabId)
627
+ || !isAvailability(stableConversationId)
628
+ || !isAvailability(stableUserTurnId)
629
+ || !isAvailability(authoritativeTabClaim)) {
630
+ throw new OperationClientError("invalid_operation_state", "The authenticated target evidence profile is invalid.");
631
+ }
632
+ return Object.freeze({
633
+ providerIdentity: providerIdentity,
634
+ stableTabId: stableTabId,
635
+ stableConversationId: stableConversationId,
636
+ stableUserTurnId: stableUserTurnId,
637
+ authoritativeTabClaim: authoritativeTabClaim,
638
+ replacementTabRecovery
639
+ });
640
+ }
641
+ function normalizeTargetEstablishment(value) {
642
+ const record = requiredObject(value, "target establishment");
643
+ const targetBindingDigest = requiredDigest(record, "targetBindingDigest");
644
+ const anchorDigest = requiredDigest(record, "anchorDigest");
645
+ const causalSendActionId = requiredBoundedString(record, "causalSendActionId");
646
+ const conversationId = requiredBoundedString(record, "conversationId");
647
+ const canonicalThreadUrl = requiredBoundedString(record, "canonicalThreadUrl");
648
+ const userTurnId = requiredBoundedString(record, "userTurnId");
649
+ const userTurnEvidenceDigest = requiredDigest(record, "userTurnEvidenceDigest");
650
+ const evidenceDigest = requiredDigest(record, "evidenceDigest");
651
+ const observedAt = requiredBoundedString(record, "observedAt");
652
+ return Object.freeze({
653
+ targetBindingDigest,
654
+ anchorDigest,
655
+ causalSendActionId,
656
+ conversationId,
657
+ canonicalThreadUrl,
658
+ userTurnId,
659
+ userTurnEvidenceDigest,
660
+ evidenceDigest,
661
+ observedAt
662
+ });
663
+ }
664
+ function makeFactoryContext(handle, state, target) {
665
+ const context = { ...handle };
666
+ Object.defineProperties(context, {
667
+ handle: { value: handle, enumerable: false, writable: false, configurable: false },
668
+ state: { value: state, enumerable: false, writable: false, configurable: false },
669
+ target: { value: target, enumerable: false, writable: false, configurable: false }
670
+ });
671
+ return Object.freeze(context);
672
+ }
673
+ function makeControlFactoryContext(request, reconstruction) {
674
+ const context = { request };
675
+ Object.defineProperties(context, {
676
+ handle: {
677
+ value: reconstruction.context.handle,
678
+ enumerable: false,
679
+ writable: false,
680
+ configurable: false
681
+ },
682
+ state: {
683
+ value: reconstruction.context.state,
684
+ enumerable: false,
685
+ writable: false,
686
+ configurable: false
687
+ },
688
+ target: {
689
+ value: reconstruction.context.target,
690
+ enumerable: false,
691
+ writable: false,
692
+ configurable: false
693
+ },
694
+ durable: {
695
+ value: reconstruction.context,
696
+ enumerable: false,
697
+ writable: false,
698
+ configurable: false
699
+ }
700
+ });
701
+ return Object.freeze(context);
702
+ }
703
+ function unavailableAdapter(code) {
704
+ const unavailable = async () => {
705
+ throw Object.freeze({ code });
706
+ };
707
+ return Object.freeze({
708
+ resolveTarget: unavailable,
709
+ submission: Object.freeze({
710
+ observeStaging: unavailable,
711
+ executeFileHandoffOnce: unavailable,
712
+ observeAttachments: unavailable,
713
+ prepareSend: unavailable,
714
+ executePreparedSend: unavailable,
715
+ verifyPreparedSend: unavailable,
716
+ recoverSend: unavailable,
717
+ executeFinalTabTransaction: unavailable
718
+ }),
719
+ collector: Object.freeze({
720
+ readContext: unavailable,
721
+ observe: unavailable,
722
+ sleep: unavailable
723
+ })
724
+ });
725
+ }
726
+ function readRequestedFiles(request) {
727
+ if (request.files === undefined)
728
+ return [];
729
+ if (!Array.isArray(request.files)) {
730
+ throw new OperationClientError("invalid_operation_request", "Operation request files must be an array.");
731
+ }
732
+ return request.files;
733
+ }
734
+ function sanitizeServiceRequest(request, identities) {
735
+ const copy = cloneFrozen(request, "invalid_operation_request", false);
736
+ if (request.files !== undefined) {
737
+ copy.files = identities.map((identity, index) => ({
738
+ path: `${SAFE_INPUT_PATH_PREFIX}${index + 1}`,
739
+ displayName: identity.manifest.displayName
740
+ }));
741
+ }
742
+ if (request.capture !== undefined && request.capture.outputDirectory !== undefined) {
743
+ copy.capture = {
744
+ ...request.capture,
745
+ outputDirectory: SAFE_OUTPUT_PATH
746
+ };
747
+ }
748
+ return freezeDeep(copy);
749
+ }
750
+ function freezeIdentity(identity) {
751
+ const snapshot = cloneDataGraph(identity, "invalid_file_identity", false);
752
+ if (snapshot === null || typeof snapshot !== "object" || snapshot.manifest === undefined || snapshot.proof === undefined) {
753
+ throw new OperationClientError("invalid_file_identity", "The operation file identity is invalid.");
754
+ }
755
+ return freezeDeep({
756
+ sourcePath: snapshot.sourcePath,
757
+ manifest: { ...snapshot.manifest },
758
+ proof: { ...snapshot.proof }
759
+ });
760
+ }
761
+ function forwardSubmitOptions(options, signal) {
762
+ return Object.freeze({
763
+ signal,
764
+ ...(options.deadlineAt === undefined ? {} : { deadlineAt: options.deadlineAt })
765
+ });
766
+ }
767
+ function forwardCollectorOptions(options) {
768
+ const forwarded = {
769
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
770
+ ...(options.wait === undefined ? {} : { wait: options.wait }),
771
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
772
+ ...(options.maxAttempts === undefined ? {} : { maxAttempts: options.maxAttempts }),
773
+ ...(options.pollIntervalMs === undefined ? {} : { pollIntervalMs: options.pollIntervalMs }),
774
+ ...(options.responseContent === undefined ? {} : { responseContent: options.responseContent }),
775
+ ...(options.responseFormat === undefined ? {} : { responseFormat: options.responseFormat }),
776
+ ...(options.now === undefined ? {} : { now: options.now })
777
+ };
778
+ return Object.freeze(forwarded);
779
+ }
780
+ function forwardControlOptions(options) {
781
+ return Object.freeze({
782
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
783
+ ...(options.deadlineAt === undefined ? {} : { deadlineAt: options.deadlineAt }),
784
+ ...(options.now === undefined ? {} : { now: options.now })
785
+ });
786
+ }
787
+ function forwardRunOptions(options, signal) {
788
+ const forwarded = {
789
+ signal,
790
+ ...(options.deadlineAt === undefined ? {} : { deadlineAt: options.deadlineAt }),
791
+ ...(options.wait === undefined ? {} : { wait: options.wait }),
792
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
793
+ ...(options.maxAttempts === undefined ? {} : { maxAttempts: options.maxAttempts }),
794
+ ...(options.pollIntervalMs === undefined ? {} : { pollIntervalMs: options.pollIntervalMs }),
795
+ ...(options.responseContent === undefined ? {} : { responseContent: options.responseContent }),
796
+ ...(options.responseFormat === undefined ? {} : { responseFormat: options.responseFormat }),
797
+ ...(options.now === undefined ? {} : { now: options.now })
798
+ };
799
+ return Object.freeze(forwarded);
800
+ }
801
+ function adapterKey(operationId, requestDigest) {
802
+ return `${operationId}\0${requestDigest}`;
803
+ }
804
+ function validateMaxCachedAdapters(value) {
805
+ const result = value ?? DEFAULT_MAX_CACHED_ADAPTERS;
806
+ if (!Number.isSafeInteger(result) || result < 1 || result > MAX_CACHED_ADAPTERS) {
807
+ throw new OperationClientError("invalid_adapter_cache_size", `maxCachedAdapters must be a positive integer no greater than ${MAX_CACHED_ADAPTERS}.`);
808
+ }
809
+ return result;
810
+ }
811
+ function isTerminalSubmitResult(result) {
812
+ return result.submission.kind === "completed_receipt";
813
+ }
814
+ function isTerminalRunResult(result) {
815
+ return result.submit.submission.kind === "completed_receipt"
816
+ || result.collect?.kind === "completed";
817
+ }
818
+ function assertAbortSignal(value) {
819
+ if (value === null || typeof value !== "object" || typeof AbortSignal !== "function") {
820
+ throw new OperationClientError("invalid_signal", "Operation signal must be an AbortSignal.");
821
+ }
822
+ const abortedGetter = Object.getOwnPropertyDescriptor(AbortSignal.prototype, "aborted")?.get;
823
+ if (abortedGetter === undefined) {
824
+ throw new OperationClientError("invalid_signal", "Operation signal must be an AbortSignal.");
825
+ }
826
+ try {
827
+ if (typeof Reflect.apply(abortedGetter, value, []) !== "boolean")
828
+ throw new Error("invalid");
829
+ }
830
+ catch {
831
+ throw new OperationClientError("invalid_signal", "Operation signal must be an AbortSignal.");
832
+ }
833
+ }
834
+ function fileBoundaryError(error) {
835
+ let candidate = "operation_file_identity_failed";
836
+ if (error !== null && typeof error === "object") {
837
+ try {
838
+ const descriptor = Object.getOwnPropertyDescriptor(error, "code");
839
+ if (descriptor !== undefined && "value" in descriptor && typeof descriptor.value === "string") {
840
+ candidate = descriptor.value;
841
+ }
842
+ }
843
+ catch {
844
+ // Treat proxies and accessors as untrusted provider input.
845
+ }
846
+ }
847
+ const code = /^[a-z][a-z0-9_]{0,63}$/.test(candidate)
848
+ ? candidate
849
+ : "operation_file_identity_failed";
850
+ return new OperationClientError(code, "The operation input file could not be established or revalidated safely.");
851
+ }
852
+ function cloneFrozen(value, code, freeze = true) {
853
+ if (value === null || typeof value !== "object") {
854
+ throw new OperationClientError(code, "The operation input is invalid.");
855
+ }
856
+ const clone = cloneDataGraph(value, code, false);
857
+ return freeze ? freezeDeep(clone) : clone;
858
+ }
859
+ function freezeDeep(value, seen = new WeakSet()) {
860
+ if (value === null || typeof value !== "object")
861
+ return value;
862
+ const object = value;
863
+ if (seen.has(object))
864
+ return value;
865
+ seen.add(object);
866
+ let descriptors;
867
+ try {
868
+ descriptors = Object.getOwnPropertyDescriptors(object);
869
+ }
870
+ catch {
871
+ throw new OperationClientError("invalid_operation_input", "The operation input could not be frozen safely.");
872
+ }
873
+ for (const descriptor of Object.values(descriptors)) {
874
+ if ("value" in descriptor)
875
+ freezeDeep(descriptor.value, seen);
876
+ }
877
+ return Object.freeze(value);
878
+ }
879
+ function freshResult(value) {
880
+ return cloneDataGraph(value, "result_clone_failed", false);
881
+ }
882
+ /**
883
+ * Bounded descriptor-only clone used at all untrusted facade boundaries.
884
+ * `structuredClone` is unsuitable here because it invokes enumerable
885
+ * getters. Unknown future fields are preserved for ordinary results, but any
886
+ * accessor, function, symbol, exotic object, cycle, or oversized graph fails
887
+ * closed with a static client error.
888
+ */
889
+ function cloneDataGraph(value, code, _freeze, depth = 0, seen = new WeakSet(), budget = { nodes: 0 }) {
890
+ if (value === null || typeof value !== "object") {
891
+ if (typeof value === "function" || typeof value === "symbol") {
892
+ throw new OperationClientError(code, "The operation data could not be copied safely.");
893
+ }
894
+ return value;
895
+ }
896
+ if (depth > MAX_SAFE_DATA_DEPTH || budget.nodes >= MAX_SAFE_DATA_NODES) {
897
+ throw new OperationClientError(code, "The operation data exceeds its safety bound.");
898
+ }
899
+ const object = value;
900
+ if (seen.has(object)) {
901
+ throw new OperationClientError(code, "The operation data contains a cycle.");
902
+ }
903
+ seen.add(object);
904
+ budget.nodes += 1;
905
+ let prototype;
906
+ let descriptors;
907
+ try {
908
+ prototype = Object.getPrototypeOf(object);
909
+ descriptors = Object.getOwnPropertyDescriptors(object);
910
+ }
911
+ catch {
912
+ throw new OperationClientError(code, "The operation data could not be copied safely.");
913
+ }
914
+ if (!Array.isArray(object) && !isPlainDataPrototype(prototype)) {
915
+ throw new OperationClientError(code, "The operation data contains an unsupported object.");
916
+ }
917
+ if (Reflect.ownKeys(descriptors).some(key => typeof key !== "string")) {
918
+ throw new OperationClientError(code, "The operation data contains an unsupported symbol property.");
919
+ }
920
+ if (Array.isArray(object)) {
921
+ const lengthDescriptor = descriptors.length;
922
+ if (lengthDescriptor === undefined || !("value" in lengthDescriptor) || typeof lengthDescriptor.value !== "number") {
923
+ throw new OperationClientError(code, "The operation data contains an invalid array.");
924
+ }
925
+ const length = lengthDescriptor.value;
926
+ if (!Number.isSafeInteger(length) || length < 0 || length > MAX_SAFE_DATA_NODES) {
927
+ throw new OperationClientError(code, "The operation data contains an oversized array.");
928
+ }
929
+ const clone = [];
930
+ for (let index = 0; index < length; index += 1) {
931
+ const descriptor = descriptors[String(index)];
932
+ if (descriptor === undefined || !("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined) {
933
+ throw new OperationClientError(code, "The operation data contains an unsafe array entry.");
934
+ }
935
+ clone.push(cloneDataGraph(descriptor.value, code, false, depth + 1, seen, budget));
936
+ }
937
+ for (const [key, descriptor] of Object.entries(descriptors)) {
938
+ if (key === "length" || /^\d+$/u.test(key))
939
+ continue;
940
+ if (!descriptor.enumerable)
941
+ continue;
942
+ if (!("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined) {
943
+ throw new OperationClientError(code, "The operation data contains an unsafe property.");
944
+ }
945
+ Object.defineProperty(clone, key, {
946
+ value: cloneDataGraph(descriptor.value, code, false, depth + 1, seen, budget),
947
+ enumerable: true,
948
+ writable: true,
949
+ configurable: true
950
+ });
951
+ }
952
+ seen.delete(object);
953
+ return clone;
954
+ }
955
+ const clone = {};
956
+ for (const [key, descriptor] of Object.entries(descriptors)) {
957
+ if (!descriptor.enumerable)
958
+ continue;
959
+ if (!("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined) {
960
+ throw new OperationClientError(code, "The operation data contains an unsafe property.");
961
+ }
962
+ Object.defineProperty(clone, key, {
963
+ value: cloneDataGraph(descriptor.value, code, false, depth + 1, seen, budget),
964
+ enumerable: true,
965
+ writable: true,
966
+ configurable: true
967
+ });
968
+ }
969
+ seen.delete(object);
970
+ return clone;
971
+ }
972
+ /**
973
+ * Recognize ordinary records from another JavaScript realm without trusting
974
+ * constructors, `Symbol.toStringTag`, or inherited accessors. A realm's
975
+ * intrinsic `Object.prototype` is itself a direct child of `null`; custom
976
+ * class instances have at least one additional prototype layer. The clone
977
+ * remains descriptor-only and always discards the source prototype.
978
+ */
979
+ function isPlainDataPrototype(prototype) {
980
+ if (prototype === null || prototype === Object.prototype)
981
+ return true;
982
+ try {
983
+ return Object.getPrototypeOf(prototype) === null;
984
+ }
985
+ catch {
986
+ return false;
987
+ }
988
+ }
989
+ function isDigest(value) {
990
+ return DIGEST_PATTERN.test(value);
991
+ }
992
+ function isOperationSurface(value) {
993
+ return value === "chat" || value === "work";
994
+ }
995
+ function isOperationPhase(value) {
996
+ return value === "prepared"
997
+ || value === "handoff_pending"
998
+ || value === "ready"
999
+ || value === "send_pending"
1000
+ || value === "submitted"
1001
+ || value === "generating"
1002
+ || value === "capturing"
1003
+ || value === "completed"
1004
+ || value === "uncertain";
1005
+ }
1006
+ function isMutationBoundary(value) {
1007
+ return value === "none"
1008
+ || value === "handoff_may_have_occurred"
1009
+ || value === "send_may_have_occurred"
1010
+ || value === "control_may_have_occurred";
1011
+ }
1012
+ function isAvailability(value) {
1013
+ return value === "required" || value === "unavailable";
1014
+ }
1015
+ const MAX_SAFE_DATA_DEPTH = 24;
1016
+ const MAX_SAFE_DATA_NODES = 4096;
1017
+ const DIGEST_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/u;
1018
+ function requiredObject(value, label) {
1019
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1020
+ throw new OperationClientError("invalid_operation_state", `The ${label} is invalid.`);
1021
+ }
1022
+ return value;
1023
+ }
1024
+ function requiredData(record, key) {
1025
+ const value = optionalDataProperty(record, key);
1026
+ if (value === undefined) {
1027
+ throw new OperationClientError("invalid_operation_state", "The authenticated operation data is incomplete.");
1028
+ }
1029
+ return value;
1030
+ }
1031
+ /** Read only an own data property; accessor descriptors are never invoked. */
1032
+ function optionalDataProperty(record, key) {
1033
+ let descriptor;
1034
+ try {
1035
+ descriptor = Object.getOwnPropertyDescriptor(record, key);
1036
+ }
1037
+ catch {
1038
+ throw new OperationClientError("invalid_operation_state", "The operation data could not be read safely.");
1039
+ }
1040
+ if (descriptor === undefined)
1041
+ return undefined;
1042
+ if (!("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined) {
1043
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an unsafe property.");
1044
+ }
1045
+ return descriptor.value;
1046
+ }
1047
+ function requiredString(record, key) {
1048
+ const value = requiredData(record, key);
1049
+ if (typeof value !== "string") {
1050
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an invalid value.");
1051
+ }
1052
+ return value;
1053
+ }
1054
+ function requiredBoundedString(record, key) {
1055
+ const value = requiredString(record, key);
1056
+ if (value.length === 0 || value.length > 4096) {
1057
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an unbounded value.");
1058
+ }
1059
+ return value;
1060
+ }
1061
+ function optionalString(record, key) {
1062
+ const value = optionalDataProperty(record, key);
1063
+ if (value === undefined)
1064
+ return undefined;
1065
+ if (typeof value !== "string") {
1066
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an invalid value.");
1067
+ }
1068
+ return value;
1069
+ }
1070
+ function optionalBoundedString(record, key) {
1071
+ const value = optionalString(record, key);
1072
+ if (value === undefined)
1073
+ return undefined;
1074
+ if (value.length === 0 || value.length > 4096) {
1075
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an unbounded value.");
1076
+ }
1077
+ return value;
1078
+ }
1079
+ function requiredSafeInteger(record, key) {
1080
+ const value = requiredData(record, key);
1081
+ if (!Number.isSafeInteger(value)) {
1082
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an invalid revision.");
1083
+ }
1084
+ return value;
1085
+ }
1086
+ function requiredBoolean(record, key) {
1087
+ const value = requiredData(record, key);
1088
+ if (typeof value !== "boolean") {
1089
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an invalid flag.");
1090
+ }
1091
+ return value;
1092
+ }
1093
+ function requiredDigest(record, key) {
1094
+ const value = requiredString(record, key);
1095
+ if (!isDigest(value)) {
1096
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an invalid digest.");
1097
+ }
1098
+ return value;
1099
+ }
1100
+ function optionalDigest(record, key) {
1101
+ const value = optionalString(record, key);
1102
+ if (value === undefined)
1103
+ return undefined;
1104
+ if (!isDigest(value)) {
1105
+ throw new OperationClientError("invalid_operation_state", "The operation data contains an invalid digest.");
1106
+ }
1107
+ return value;
1108
+ }
1109
+ function requiredMethod(record, key) {
1110
+ const value = optionalDataProperty(record, key);
1111
+ if (typeof value !== "function") {
1112
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter is incomplete.");
1113
+ }
1114
+ // Bind the captured function to the exact validated owner. The returned
1115
+ // callable no longer consults a mutable property or depends on the caller's
1116
+ // `this`, while the descriptor-only read above keeps accessors/proxies out
1117
+ // of the validation path.
1118
+ return Reflect.apply(Function.prototype.bind, value, [record]);
1119
+ }
1120
+ /** Read and bind an optional method using the same accessor-free boundary. */
1121
+ function optionalMethod(record, key) {
1122
+ const value = optionalDataProperty(record, key);
1123
+ if (value === undefined)
1124
+ return undefined;
1125
+ if (typeof value !== "function") {
1126
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter contains an invalid optional method.");
1127
+ }
1128
+ return Reflect.apply(Function.prototype.bind, value, [record]);
1129
+ }
1130
+ function requiredAdapterObject(record, key) {
1131
+ if (record === null || typeof record !== "object") {
1132
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter is incomplete.");
1133
+ }
1134
+ const value = optionalDataProperty(record, key);
1135
+ if (value === undefined) {
1136
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter is incomplete.");
1137
+ }
1138
+ return adapterObject(value);
1139
+ }
1140
+ function adapterObject(value) {
1141
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1142
+ throw new OperationClientError("adapter_unavailable", "The operation browser adapter is incomplete.");
1143
+ }
1144
+ return value;
1145
+ }