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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +27 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +40 -1
  4. package/contracts/v1/fixtures/backend-compatibility.json +21 -0
  5. package/contracts/v1/fixtures/backend-version.json +6 -1
  6. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  7. package/contracts/v1/fixtures/operation-action.json +13 -0
  8. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  9. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  10. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  11. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  12. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  13. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  14. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  15. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  16. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  17. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  18. package/contracts/v1/fixtures/operation-event.json +18 -0
  19. package/contracts/v1/fixtures/operation-handle.json +10 -0
  20. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  21. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  22. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  23. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  24. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  25. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  26. package/contracts/v1/fixtures/operation-request.json +33 -0
  27. package/contracts/v1/fixtures/operation-state.json +144 -0
  28. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  29. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  30. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  31. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  32. package/contracts/v1/manifest.json +155 -1
  33. package/contracts/v1/parity-suite.json +96 -1
  34. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  35. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  36. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  37. package/contracts/v1/schemas/manifest.schema.json +57 -0
  38. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  39. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  40. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  41. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  42. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  43. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  44. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  45. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  46. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  47. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  48. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  49. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  50. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  51. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  52. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  53. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  54. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  55. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  56. package/contracts/v1/surface-drift-policy.json +50 -0
  57. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  58. package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
  59. package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
  60. package/dist/src/backend/client.d.ts +103 -2
  61. package/dist/src/backend/client.js +1436 -97
  62. package/dist/src/backend/compatibility.d.ts +12 -0
  63. package/dist/src/backend/compatibility.js +208 -0
  64. package/dist/src/backend/protocol.d.ts +84 -1
  65. package/dist/src/backend/protocol.js +31 -5
  66. package/dist/src/backend/runtime-identity.d.ts +10 -0
  67. package/dist/src/backend/runtime-identity.js +141 -0
  68. package/dist/src/backend/session.d.ts +10 -2
  69. package/dist/src/backend/session.js +478 -7
  70. package/dist/src/backend/stdio-server.d.ts +4 -1
  71. package/dist/src/backend/stdio-server.js +240 -28
  72. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  73. package/dist/src/browser/active-composer-file-input.js +33 -0
  74. package/dist/src/browser/attach.d.ts +4 -1
  75. package/dist/src/browser/attach.js +136 -28
  76. package/dist/src/browser/downloads.d.ts +2 -0
  77. package/dist/src/client.d.ts +38 -0
  78. package/dist/src/client.js +1222 -59
  79. package/dist/src/commands/configuration.js +13 -7
  80. package/dist/src/commands/doctor.d.ts +1 -1
  81. package/dist/src/commands/doctor.js +26 -3
  82. package/dist/src/commands/experience.js +35 -0
  83. package/dist/src/commands/files.js +4 -27
  84. package/dist/src/commands/modes.js +49 -17
  85. package/dist/src/commands/power-discovery.d.ts +130 -0
  86. package/dist/src/commands/power-discovery.js +627 -0
  87. package/dist/src/commands/project-sources.js +325 -31
  88. package/dist/src/commands/sequence.js +59 -32
  89. package/dist/src/commands/session.js +7 -2
  90. package/dist/src/commands/work.js +31 -0
  91. package/dist/src/errors.d.ts +9 -0
  92. package/dist/src/errors.js +22 -0
  93. package/dist/src/index.d.ts +6 -0
  94. package/dist/src/index.js +9 -0
  95. package/dist/src/operations/artifact-output.d.ts +69 -0
  96. package/dist/src/operations/artifact-output.js +1748 -0
  97. package/dist/src/operations/artifact-stream.d.ts +19 -0
  98. package/dist/src/operations/artifact-stream.js +35 -0
  99. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  100. package/dist/src/operations/artifact-transfer.js +1312 -0
  101. package/dist/src/operations/browser-adapter.d.ts +161 -0
  102. package/dist/src/operations/browser-adapter.js +2023 -0
  103. package/dist/src/operations/browser-observation.d.ts +110 -0
  104. package/dist/src/operations/browser-observation.js +1208 -0
  105. package/dist/src/operations/browser-target.d.ts +103 -0
  106. package/dist/src/operations/browser-target.js +489 -0
  107. package/dist/src/operations/canonical.d.ts +4 -0
  108. package/dist/src/operations/canonical.js +412 -0
  109. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  110. package/dist/src/operations/chatgpt-runtime.js +1150 -0
  111. package/dist/src/operations/client.d.ts +138 -0
  112. package/dist/src/operations/client.js +1143 -0
  113. package/dist/src/operations/collector.d.ts +195 -0
  114. package/dist/src/operations/collector.js +1039 -0
  115. package/dist/src/operations/control.d.ts +372 -0
  116. package/dist/src/operations/control.js +1498 -0
  117. package/dist/src/operations/file-identity.d.ts +33 -0
  118. package/dist/src/operations/file-identity.js +153 -0
  119. package/dist/src/operations/handle.d.ts +19 -0
  120. package/dist/src/operations/handle.js +727 -0
  121. package/dist/src/operations/index.d.ts +22 -0
  122. package/dist/src/operations/index.js +22 -0
  123. package/dist/src/operations/journal.d.ts +142 -0
  124. package/dist/src/operations/journal.js +1931 -0
  125. package/dist/src/operations/production-attachments.d.ts +116 -0
  126. package/dist/src/operations/production-attachments.js +1018 -0
  127. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  128. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  129. package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
  130. package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
  131. package/dist/src/operations/production-configuration.d.ts +42 -0
  132. package/dist/src/operations/production-configuration.js +1363 -0
  133. package/dist/src/operations/production-primitives.d.ts +53 -0
  134. package/dist/src/operations/production-primitives.js +1073 -0
  135. package/dist/src/operations/production-work-steer.d.ts +203 -0
  136. package/dist/src/operations/production-work-steer.js +1218 -0
  137. package/dist/src/operations/recovery.d.ts +80 -0
  138. package/dist/src/operations/recovery.js +174 -0
  139. package/dist/src/operations/runtime-adapter.d.ts +118 -0
  140. package/dist/src/operations/runtime-adapter.js +786 -0
  141. package/dist/src/operations/send-once.d.ts +224 -0
  142. package/dist/src/operations/send-once.js +1081 -0
  143. package/dist/src/operations/service.d.ts +281 -0
  144. package/dist/src/operations/service.js +2810 -0
  145. package/dist/src/operations/staging.d.ts +136 -0
  146. package/dist/src/operations/staging.js +630 -0
  147. package/dist/src/operations/state-machine.d.ts +29 -0
  148. package/dist/src/operations/state-machine.js +2047 -0
  149. package/dist/src/operations/submission.d.ts +423 -0
  150. package/dist/src/operations/submission.js +1676 -0
  151. package/dist/src/operations/turn-ownership.d.ts +202 -0
  152. package/dist/src/operations/turn-ownership.js +700 -0
  153. package/dist/src/operations/types.d.ts +454 -0
  154. package/dist/src/operations/types.js +20 -0
  155. package/dist/src/operations/wire-requests.d.ts +21 -0
  156. package/dist/src/operations/wire-requests.js +396 -0
  157. package/dist/src/operations/wire-results.d.ts +97 -0
  158. package/dist/src/operations/wire-results.js +818 -0
  159. package/dist/src/runner/responses.d.ts +3 -1
  160. package/dist/src/runner/responses.js +19 -1
  161. package/dist/src/runner/result.js +227 -55
  162. package/dist/src/runner/types.d.ts +12 -0
  163. package/dist/src/runtime/command-routing.d.ts +149 -0
  164. package/dist/src/runtime/command-routing.js +431 -0
  165. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  166. package/dist/src/runtime/coordinated-browser.js +315 -0
  167. package/dist/src/runtime/coordinated-page.d.ts +39 -0
  168. package/dist/src/runtime/coordinated-page.js +634 -0
  169. package/dist/src/runtime/operation-context.d.ts +142 -0
  170. package/dist/src/runtime/operation-context.js +410 -0
  171. package/dist/src/runtime/runtime-session.d.ts +95 -0
  172. package/dist/src/runtime/runtime-session.js +314 -0
  173. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  174. package/dist/src/runtime/tab-coordinator.js +1200 -0
  175. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  176. package/dist/src/runtime/value-boundaries.js +49 -0
  177. package/dist/src/safety/untrusted-output.js +2 -1
  178. package/dist/src/scripts/backend-server.js +4 -1
  179. package/dist/src/scripts/live-smoke/harness.js +79 -7
  180. package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
  181. package/dist/src/scripts/live-smoke/scenarios.js +110 -18
  182. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  183. package/dist/src/scripts/release-canary-module.js +17 -4
  184. package/dist/src/types.d.ts +38 -1
  185. package/package.json +1 -1
  186. package/references/2026-08-16-transactional-operations.md +459 -0
  187. package/references/agents-runner.md +34 -0
  188. package/references/backend-protocol.md +73 -0
  189. package/references/python-parity.md +72 -0
  190. package/references/responses-adapter.md +19 -0
  191. package/references/streaming.md +6 -0
@@ -0,0 +1,1018 @@
1
+ import { types as nodeTypes } from "node:util";
2
+ import { coordinatedEventRegistrationBarrier } from "../runtime/coordinated-page.js";
3
+ import { isPlainDataRecord } from "../runtime/value-boundaries.js";
4
+ export const PRODUCTION_ATTACHMENT_SCHEMA_VERSION = "chatgpt.browser_control.production_attachments.v1";
5
+ const DIGEST_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/u;
6
+ const ID_PATTERN = /^[A-Za-z0-9._:-]{1,512}$/u;
7
+ const CAPABILITY_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9._:-]{0,127}$/u;
8
+ const MAX_FILES = 256;
9
+ const DEFAULT_MAX_CANDIDATES = 128;
10
+ const MAX_CANDIDATES = 512;
11
+ const DEFAULT_TIMEOUT_MS = 5_000;
12
+ const MAX_TIMEOUT_MS = 30_000;
13
+ const MAX_DEADLINE_AT = Date.UTC(2100, 0, 1);
14
+ const MAX_GRAPH_DEPTH = 12;
15
+ const MAX_GRAPH_NODES = 4_096;
16
+ /**
17
+ * Build one request-scoped, non-repeatable attachment capability.
18
+ *
19
+ * The factory validates and freezes the entire identity graph before any
20
+ * provider callback can run. Handoff has one state machine: revalidate -> arm
21
+ * one chooser waiter -> resolve one visible control -> click at most once ->
22
+ * setFiles at most once. If either browser mutation can have happened and its
23
+ * outcome is not exact, the result is quarantined as uncertain.
24
+ */
25
+ export function createProductionAttachmentPrimitive(options) {
26
+ const normalized = normalizeOptions(options);
27
+ const snapshot = snapshotFiles(normalized.files, normalized.identityDigest);
28
+ let handoffConsumed = false;
29
+ const observeAttachments = async (request, page, target) => {
30
+ const normalizedRequest = normalizeAttachmentRequest(request, snapshot);
31
+ if (normalizedRequest === undefined || !isSafeProviderObject(page) || !isSafeDataGraph(target)) {
32
+ return { status: "unavailable" };
33
+ }
34
+ let read;
35
+ try {
36
+ const result = normalized.observeSurface(cloneAttachmentRequest(normalizedRequest), page, target);
37
+ read = await boundedCallback(result, normalized.timeoutMs);
38
+ }
39
+ catch {
40
+ return { status: "unavailable" };
41
+ }
42
+ return normalizeSurfaceObservation(normalizedRequest, read, normalized.evidenceDigest);
43
+ };
44
+ const handoffFiles = async (request, page, target) => {
45
+ const normalizedRequest = normalizeHandoffRequest(request, snapshot);
46
+ if (normalizedRequest === undefined || !isSafeProviderObject(page) || !isSafeDataGraph(target)) {
47
+ return { status: "not_satisfied", blockerCode: "attachment_manifest_mismatch" };
48
+ }
49
+ if (normalizedRequest.manifest.count === 0) {
50
+ return { status: "not_satisfied", blockerCode: "attachment_manifest_mismatch" };
51
+ }
52
+ if (handoffConsumed) {
53
+ return { status: "uncertain", quarantine: "caller" };
54
+ }
55
+ handoffConsumed = true;
56
+ // The request deadline is the coordinator's absolute deadline. The
57
+ // provider timeout remains a local upper bound, so a provider callback can
58
+ // never outlive either budget. Once this request has entered the one-shot
59
+ // handoff state, any caller cancellation is observation-only even when it
60
+ // arrives before the first browser mutation.
61
+ const requestSignal = normalizedRequest.signal;
62
+ const requestDeadlineAt = normalizedRequest.deadlineAt;
63
+ const deadlineAt = Math.min(requestDeadlineAt ?? MAX_DEADLINE_AT, Date.now() + normalized.timeoutMs);
64
+ const requestCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
65
+ if (requestCancellation !== undefined)
66
+ return requestCancellation;
67
+ const revalidated = await revalidateSnapshot(snapshot, normalized.revalidateFile, deadlineAt);
68
+ const afterRevalidationCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
69
+ if (afterRevalidationCancellation !== undefined)
70
+ return afterRevalidationCancellation;
71
+ if (revalidated === "changed") {
72
+ return { status: "not_satisfied", blockerCode: "input_file_changed" };
73
+ }
74
+ if (revalidated === "timeout") {
75
+ return { status: "not_satisfied", blockerCode: "operation_timeout" };
76
+ }
77
+ const chooserBudget = remainingBudget(deadlineAt);
78
+ if (chooserBudget <= 0)
79
+ return { status: "not_satisfied", blockerCode: "operation_timeout" };
80
+ const waiter = startChooserWait(page, chooserBudget, requestSignal);
81
+ // A coordinated page registers the provider listener through its tab
82
+ // actor. Prove that short registration transaction completed, then drain
83
+ // one host turn for any outcome that was already scheduled before this
84
+ // operation's first activation. Such an outcome cannot be causal to the
85
+ // not-yet-issued click and must block the handoff.
86
+ const beforeActivation = await settleChooserBeforeMutation(waiter, deadlineAt, requestSignal);
87
+ const afterActivationFenceCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
88
+ if (afterActivationFenceCancellation !== undefined)
89
+ return afterActivationFenceCancellation;
90
+ if (beforeActivation !== undefined) {
91
+ // A chooser that settled before this invocation's activation may belong
92
+ // to a human or an earlier browser action. Never feed request files to
93
+ // an event whose causal activation this primitive cannot prove.
94
+ if (beforeActivation.kind === "success")
95
+ return { status: "not_satisfied", blockerCode: "ambiguous_file_handoff" };
96
+ return beforeActivation.kind === "timeout"
97
+ ? { status: "not_satisfied", blockerCode: "operation_timeout" }
98
+ : { status: "not_satisfied", blockerCode: "ambiguous_file_handoff" };
99
+ }
100
+ if (normalized.prepareActivation !== undefined) {
101
+ const preparationBudget = remainingBudget(deadlineAt);
102
+ if (preparationBudget <= 0)
103
+ return { status: "not_satisfied", blockerCode: "operation_timeout" };
104
+ const beforePreparationCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
105
+ if (beforePreparationCancellation !== undefined)
106
+ return beforePreparationCancellation;
107
+ let preparation;
108
+ try {
109
+ const rawPreparation = normalized.prepareActivation(cloneHandoffRequest(normalizedRequest), page, target, { timeoutMs: preparationBudget });
110
+ preparation = await awaitMutatingCallback(rawPreparation);
111
+ }
112
+ catch {
113
+ return { status: "uncertain", quarantine: "provider" };
114
+ }
115
+ const afterPreparationCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
116
+ if (afterPreparationCancellation !== undefined)
117
+ return afterPreparationCancellation;
118
+ if (!isPlainDataRecord(preparation))
119
+ return { status: "uncertain", quarantine: "provider" };
120
+ const preparationStatus = readData(preparation, "status");
121
+ if (preparationStatus === "not_satisfied") {
122
+ const blockerCode = readData(preparation, "blockerCode");
123
+ return blockerCode === "selector_drift" || blockerCode === "ambiguous_file_handoff" || blockerCode === "operation_timeout"
124
+ ? { status: "not_satisfied", blockerCode }
125
+ : { status: "uncertain", quarantine: "provider" };
126
+ }
127
+ if (preparationStatus === "uncertain") {
128
+ const quarantine = readData(preparation, "quarantine");
129
+ return quarantine === "provider" || quarantine === "caller"
130
+ ? { status: "uncertain", quarantine }
131
+ : { status: "uncertain", quarantine: "provider" };
132
+ }
133
+ const providerEvidenceDigest = readData(preparation, "providerEvidenceDigest");
134
+ if (preparationStatus !== "prepared" || !isDigest(providerEvidenceDigest)) {
135
+ return { status: "uncertain", quarantine: "provider" };
136
+ }
137
+ // If the preparation mutation delivered a chooser, it is not ours to
138
+ // consume unless the final activation below also occurs after this
139
+ // handoff invocation. A rejection means preparation may have acted.
140
+ const afterPreparation = await settleChooserBeforeMutation(waiter, deadlineAt, requestSignal);
141
+ if (afterPreparation !== undefined) {
142
+ return { status: "uncertain", quarantine: "provider" };
143
+ }
144
+ }
145
+ let activation;
146
+ const beforeResolveCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
147
+ if (beforeResolveCancellation !== undefined)
148
+ return beforeResolveCancellation;
149
+ try {
150
+ const result = normalized.resolveActivation(cloneHandoffRequest(normalizedRequest), page, target);
151
+ activation = await boundedCallback(result, remainingBudget(deadlineAt));
152
+ }
153
+ catch {
154
+ return { status: "not_satisfied", blockerCode: "selector_drift" };
155
+ }
156
+ const afterResolveCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
157
+ if (afterResolveCancellation !== undefined)
158
+ return afterResolveCancellation;
159
+ const beforeFinalActivation = await settleChooserBeforeMutation(waiter, deadlineAt, requestSignal);
160
+ if (beforeFinalActivation !== undefined) {
161
+ return { status: "not_satisfied", blockerCode: "ambiguous_file_handoff" };
162
+ }
163
+ if (activation === undefined || !validateActivation(activation, normalized.maxCandidates)) {
164
+ return { status: "not_satisfied", blockerCode: "selector_drift" };
165
+ }
166
+ const locator = readData(activation, "locator");
167
+ const providerActivation = safeMethod(activation, "activate");
168
+ let activate;
169
+ if (locator !== undefined) {
170
+ const locatorCount = await readLocatorCount(locator, remainingBudget(deadlineAt));
171
+ if (locatorCount !== 1) {
172
+ return { status: "not_satisfied", blockerCode: "selector_drift" };
173
+ }
174
+ const visible = await readLocatorVisible(locator, remainingBudget(deadlineAt));
175
+ if (visible !== true) {
176
+ return { status: "not_satisfied", blockerCode: "selector_drift" };
177
+ }
178
+ const click = safeMethod(locator, "click");
179
+ if (click === undefined) {
180
+ return { status: "not_satisfied", blockerCode: "selector_drift" };
181
+ }
182
+ activate = timeoutMs => click.call(locator, { timeout: timeoutMs, timeoutMs });
183
+ }
184
+ else if (providerActivation !== undefined) {
185
+ activate = timeoutMs => providerActivation.call(activation, { timeoutMs });
186
+ }
187
+ if (activate === undefined)
188
+ return { status: "not_satisfied", blockerCode: "selector_drift" };
189
+ const clickBudget = remainingBudget(deadlineAt);
190
+ if (clickBudget <= 0)
191
+ return { status: "not_satisfied", blockerCode: "operation_timeout" };
192
+ const beforeClickCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
193
+ if (beforeClickCancellation !== undefined)
194
+ return beforeClickCancellation;
195
+ try {
196
+ await awaitMutatingCallback(activate(clickBudget));
197
+ }
198
+ catch {
199
+ // The chooser reconciliation below is authoritative. A bridge rejection
200
+ // may have followed a delivered browser gesture.
201
+ }
202
+ const afterClickCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
203
+ if (afterClickCancellation !== undefined)
204
+ return afterClickCancellation;
205
+ // The activation has crossed the browser mutation boundary and its click
206
+ // promise has settled. A chooser event is an observation, not a pending
207
+ // mutation: bound that wait so a provider that ignores its own event
208
+ // timeout cannot strand the operation forever. A late chooser is merely
209
+ // recorded by the handled promise; no continuation can call setFiles.
210
+ // Once click has crossed the browser boundary, keep the native chooser
211
+ // reconciliation alive long enough for a late event even when the
212
+ // provider-local pre-activation budget elapsed. A caller/coordinator abort
213
+ // still short-circuits this observation and forbids setFiles.
214
+ const chooserTimeout = requestDeadlineAt === undefined
215
+ ? normalized.timeoutMs
216
+ : Math.max(0, Math.min(normalized.timeoutMs, requestDeadlineAt - Date.now()));
217
+ const chooser = await awaitChooser(waiter, chooserTimeout, requestSignal);
218
+ if (chooser.kind === "aborted")
219
+ return { status: "uncertain", quarantine: "caller" };
220
+ if (chooser.kind === "success") {
221
+ const beforeSetFilesCancellation = handoffCancellation(requestSignal, requestDeadlineAt);
222
+ if (beforeSetFilesCancellation !== undefined)
223
+ return beforeSetFilesCancellation;
224
+ const result = await setChooserFilesOnce(chooser.chooser, snapshot, normalizedRequest, normalized, requestDeadlineAt === undefined
225
+ ? normalized.timeoutMs
226
+ : Math.max(0, Math.min(normalized.timeoutMs, requestDeadlineAt - Date.now())));
227
+ // A click that rejected after delivering the gesture can still produce
228
+ // an exact chooser/setFiles result. Evidence remains authoritative.
229
+ return result;
230
+ }
231
+ if (chooser.kind === "timeout") {
232
+ return handoffCancellation(requestSignal, requestDeadlineAt)
233
+ ?? { status: "uncertain", quarantine: "provider" };
234
+ }
235
+ // A visible activation occurred, but the chooser rejected. It is no longer
236
+ // safe to claim that no provider-side mutation happened.
237
+ return handoffCancellation(requestSignal, requestDeadlineAt)
238
+ ?? { status: "uncertain", quarantine: "provider" };
239
+ };
240
+ const handoffFilesForAdapter = async (request, files, page, target) => {
241
+ if (!sameIdentityList(files, snapshot.files)) {
242
+ return { status: "not_satisfied", blockerCode: "attachment_manifest_mismatch" };
243
+ }
244
+ return await handoffFiles(request, page, target);
245
+ };
246
+ return Object.freeze({
247
+ observeAttachments,
248
+ handoffFiles,
249
+ handoffFilesForAdapter
250
+ });
251
+ }
252
+ /** Alias retained for integrations that name the layer after its provider. */
253
+ export const createOperationProductionAttachments = createProductionAttachmentPrimitive;
254
+ function normalizeOptions(options) {
255
+ if (!isPlainDataRecord(options))
256
+ throw new Error("invalid attachment options");
257
+ const evidenceDigestValue = readData(options, "evidenceDigest");
258
+ const files = readData(options, "files");
259
+ const identityDigestValue = readData(options, "identityDigest");
260
+ const revalidateValue = readData(options, "revalidateFile");
261
+ const observeSurfaceValue = readData(options, "observeSurface");
262
+ const resolveActivationValue = readData(options, "resolveActivation");
263
+ const prepareActivationValue = readData(options, "prepareActivation");
264
+ const timeoutValue = readData(options, "timeoutMs");
265
+ const maxCandidateValue = readData(options, "maxCandidates");
266
+ if (typeof evidenceDigestValue !== "function"
267
+ || typeof identityDigestValue !== "function"
268
+ || !Array.isArray(files)
269
+ || typeof observeSurfaceValue !== "function"
270
+ || typeof resolveActivationValue !== "function"
271
+ || typeof revalidateValue !== "function"
272
+ || (prepareActivationValue !== undefined && typeof prepareActivationValue !== "function")) {
273
+ throw new Error("invalid attachment options");
274
+ }
275
+ const timeoutMs = timeoutValue === undefined ? DEFAULT_TIMEOUT_MS : timeoutValue;
276
+ const maxCandidates = maxCandidateValue === undefined ? DEFAULT_MAX_CANDIDATES : maxCandidateValue;
277
+ if (!isPositiveSafeInteger(timeoutMs) || timeoutMs > MAX_TIMEOUT_MS) {
278
+ throw new Error("invalid attachment timeout");
279
+ }
280
+ if (!isPositiveSafeInteger(maxCandidates) || maxCandidates > MAX_CANDIDATES) {
281
+ throw new Error("invalid attachment candidate bound");
282
+ }
283
+ const normalizedFiles = cloneIdentityList(files);
284
+ return {
285
+ files: normalizedFiles,
286
+ identityDigest: identityDigestValue,
287
+ evidenceDigest: evidenceDigestValue,
288
+ revalidateFile: revalidateValue,
289
+ observeSurface: observeSurfaceValue,
290
+ resolveActivation: resolveActivationValue,
291
+ prepareActivation: prepareActivationValue,
292
+ timeoutMs,
293
+ maxCandidates
294
+ };
295
+ }
296
+ function snapshotFiles(files, identityDigest) {
297
+ if (files.length > MAX_FILES)
298
+ throw new Error("attachment manifest exceeds its bounded item limit");
299
+ const identityDigests = [];
300
+ for (let ordinal = 0; ordinal < files.length; ordinal += 1) {
301
+ const identity = files[ordinal];
302
+ if (identity === undefined)
303
+ throw new Error("attachment identity is missing");
304
+ let digest;
305
+ try {
306
+ digest = identityDigest(ordinal, identity.manifest);
307
+ }
308
+ catch {
309
+ throw new Error("attachment identity digest failed");
310
+ }
311
+ if (!isDigest(digest) || identityDigests.includes(digest))
312
+ throw new Error("attachment identity digest is invalid");
313
+ identityDigests.push(digest);
314
+ }
315
+ return Object.freeze({
316
+ files: Object.freeze([...files]),
317
+ identityDigests: Object.freeze(identityDigests)
318
+ });
319
+ }
320
+ function normalizeAttachmentRequest(request, snapshot) {
321
+ if (!isPlainDataRecord(request))
322
+ return undefined;
323
+ const operationId = readData(request, "operationId");
324
+ const requestDigest = readData(request, "requestDigest");
325
+ const surface = readData(request, "surface");
326
+ const targetBindingDigest = readData(request, "targetBindingDigest");
327
+ const manifest = normalizeManifest(readData(request, "manifest"), snapshot);
328
+ if (typeof operationId !== "string" || !ID_PATTERN.test(operationId)
329
+ || !isDigest(requestDigest)
330
+ || (surface !== "chat" && surface !== "work")
331
+ || !isDigest(targetBindingDigest)
332
+ || manifest === undefined)
333
+ return undefined;
334
+ return { operationId, requestDigest, surface, targetBindingDigest, manifest };
335
+ }
336
+ function normalizeHandoffRequest(request, snapshot) {
337
+ if (!isPlainDataRecord(request))
338
+ return undefined;
339
+ const operationId = readData(request, "operationId");
340
+ const requestDigest = readData(request, "requestDigest");
341
+ const surface = readData(request, "surface");
342
+ const actionId = readData(request, "actionId");
343
+ const targetBindingDigest = readData(request, "targetBindingDigest");
344
+ const manifest = normalizeManifest(readData(request, "manifest"), snapshot);
345
+ const signal = readData(request, "signal");
346
+ const deadlineAt = readData(request, "deadlineAt");
347
+ if (typeof operationId !== "string" || !ID_PATTERN.test(operationId)
348
+ || !isDigest(requestDigest)
349
+ || (surface !== "chat" && surface !== "work")
350
+ || typeof actionId !== "string" || !ID_PATTERN.test(actionId)
351
+ || !isDigest(targetBindingDigest)
352
+ || manifest === undefined
353
+ || signal !== undefined && !isAbortSignal(signal)
354
+ || deadlineAt !== undefined && (!Number.isSafeInteger(deadlineAt) || deadlineAt < 0 || deadlineAt > MAX_DEADLINE_AT))
355
+ return undefined;
356
+ return {
357
+ operationId,
358
+ requestDigest,
359
+ surface,
360
+ actionId,
361
+ targetBindingDigest,
362
+ manifest,
363
+ ...(signal === undefined ? {} : { signal }),
364
+ ...(deadlineAt === undefined ? {} : { deadlineAt })
365
+ };
366
+ }
367
+ function normalizeManifest(value, snapshot) {
368
+ if (!isPlainDataRecord(value))
369
+ return undefined;
370
+ const count = readData(value, "count");
371
+ const orderPolicy = readData(value, "orderPolicy");
372
+ const identities = readData(value, "identities");
373
+ if (!isNonnegativeSafeInteger(count) || count > MAX_FILES || orderPolicy !== "exact" || !Array.isArray(identities) || !hasSafeArrayDescriptors(identities) || identities.length !== count || count !== snapshot.files.length)
374
+ return undefined;
375
+ const result = [];
376
+ const seen = new Set();
377
+ for (let ordinal = 0; ordinal < count; ordinal += 1) {
378
+ const entry = identities[ordinal];
379
+ if (!isPlainDataRecord(entry))
380
+ return undefined;
381
+ const identityDigest = readData(entry, "identityDigest");
382
+ const entryOrdinal = readData(entry, "ordinal");
383
+ if (!isDigest(identityDigest) || entryOrdinal !== ordinal || identityDigest !== snapshot.identityDigests[ordinal] || seen.has(identityDigest))
384
+ return undefined;
385
+ seen.add(identityDigest);
386
+ result.push({ identityDigest, ordinal });
387
+ }
388
+ return {
389
+ count,
390
+ orderPolicy: "exact",
391
+ identities: Object.freeze(result)
392
+ };
393
+ }
394
+ function normalizeSurfaceObservation(request, read, evidenceDigest) {
395
+ if (!isPlainDataRecord(read))
396
+ return { status: "unavailable" };
397
+ const status = readData(read, "status");
398
+ const source = readData(read, "source");
399
+ const providerEvidenceDigest = readData(read, "providerEvidenceDigest");
400
+ if (source !== "live_surface" || typeof status !== "string")
401
+ return { status: "unavailable" };
402
+ if (providerEvidenceDigest !== undefined && !isDigest(providerEvidenceDigest))
403
+ return { status: "unavailable" };
404
+ if (status === "mismatch" || status === "delayed" || status === "ambiguous" || status === "unavailable") {
405
+ const evidence = safeEvidence(evidenceDigest, "attachment-surface", {
406
+ operationId: request.operationId,
407
+ requestDigest: request.requestDigest,
408
+ targetBindingDigest: request.targetBindingDigest,
409
+ status,
410
+ providerEvidenceDigest
411
+ });
412
+ return evidence === undefined ? { status } : { status, evidenceDigest: evidence };
413
+ }
414
+ const count = readData(read, "count");
415
+ const identityDigests = readData(read, "identityDigests");
416
+ if (!isNonnegativeSafeInteger(count) || count > MAX_FILES || !Array.isArray(identityDigests) || !hasSafeArrayDescriptors(identityDigests) || identityDigests.length !== count) {
417
+ return { status: "unavailable" };
418
+ }
419
+ const observed = [];
420
+ const seen = new Set();
421
+ for (let index = 0; index < identityDigests.length; index += 1) {
422
+ const identity = identityDigests[index];
423
+ if (typeof identity !== "string" || !isDigest(identity) || seen.has(identity))
424
+ return { status: "ambiguous" };
425
+ seen.add(identity);
426
+ observed.push(identity);
427
+ }
428
+ if (status === "absent") {
429
+ // `request.manifest` is the desired non-empty envelope. An exact empty
430
+ // live surface is the precondition that authorizes the one durable
431
+ // handoff; requiring the desired manifest itself to be empty would make
432
+ // every real upload unreachable.
433
+ if (count !== 0 || observed.length !== 0 || request.manifest.count === 0 || providerEvidenceDigest === undefined)
434
+ return { status: "mismatch" };
435
+ const evidence = safeEvidence(evidenceDigest, "attachment-surface", {
436
+ operationId: request.operationId,
437
+ requestDigest: request.requestDigest,
438
+ targetBindingDigest: request.targetBindingDigest,
439
+ status,
440
+ count: 0,
441
+ identityDigests: [],
442
+ providerEvidenceDigest
443
+ });
444
+ return evidence === undefined
445
+ ? { status: "unavailable" }
446
+ : { status: "absent", evidenceDigest: evidence, count: 0, orderPolicy: "exact", identityDigests: [] };
447
+ }
448
+ if (status !== "exact")
449
+ return { status: "unavailable" };
450
+ const exact = providerEvidenceDigest !== undefined
451
+ && count === request.manifest.count
452
+ && observed.every((identity, index) => identity === request.manifest.identities[index]?.identityDigest);
453
+ const evidence = safeEvidence(evidenceDigest, "attachment-surface", {
454
+ operationId: request.operationId,
455
+ requestDigest: request.requestDigest,
456
+ targetBindingDigest: request.targetBindingDigest,
457
+ status,
458
+ count,
459
+ identityDigests: observed,
460
+ providerEvidenceDigest
461
+ });
462
+ if (!exact)
463
+ return evidence === undefined ? { status: "mismatch" } : { status: "mismatch", evidenceDigest: evidence };
464
+ return evidence === undefined
465
+ ? { status: "unavailable" }
466
+ : { status: "exact", evidenceDigest: evidence, count, orderPolicy: "exact", identityDigests: observed };
467
+ }
468
+ async function revalidateSnapshot(snapshot, revalidateFile, deadlineAt) {
469
+ for (const identity of snapshot.files) {
470
+ const budget = remainingBudget(deadlineAt);
471
+ if (budget <= 0)
472
+ return "timeout";
473
+ try {
474
+ const result = revalidateFile(identity);
475
+ await boundedCallback(result, budget);
476
+ }
477
+ catch {
478
+ if (remainingBudget(deadlineAt) <= 0)
479
+ return "timeout";
480
+ return "changed";
481
+ }
482
+ }
483
+ return "ok";
484
+ }
485
+ async function setChooserFilesOnce(chooser, snapshot, request, options, timeoutMs) {
486
+ if (timeoutMs <= 0)
487
+ return { status: "uncertain", quarantine: "provider" };
488
+ const beforeMutationCancellation = handoffCancellation(request.signal, request.deadlineAt);
489
+ if (beforeMutationCancellation !== undefined)
490
+ return beforeMutationCancellation;
491
+ // Chrome exposes FileChooser as a provider proxy around an object with
492
+ // private fields. Descriptor-walking to the prototype and calling that raw
493
+ // method with the proxy as `this` loses the provider's required binding.
494
+ // Read this one trusted capability through the proxy and retain its exact
495
+ // receiver, as the normal `chooser.setFiles(...)` API does.
496
+ const setFiles = providerCallable(chooser, "setFiles");
497
+ if (setFiles === undefined)
498
+ return { status: "uncertain", quarantine: "provider" };
499
+ const paths = snapshot.files.map(identity => identity.sourcePath);
500
+ let rawResult;
501
+ try {
502
+ rawResult = setFiles(paths, { timeout: timeoutMs, timeoutMs });
503
+ }
504
+ catch {
505
+ return { status: "uncertain", quarantine: "provider" };
506
+ }
507
+ try {
508
+ await awaitMutatingCallback(rawResult);
509
+ }
510
+ catch {
511
+ return { status: "uncertain", quarantine: "provider" };
512
+ }
513
+ // A cancellation during setFiles cannot prove whether the provider accepted
514
+ // the local paths. Awaiting the native promise above prevents actor release,
515
+ // but the result is still explicitly uncertain and must be reconciled by a
516
+ // later observation rather than retried.
517
+ if (request.signal?.aborted || request.deadlineAt !== undefined && Date.now() >= request.deadlineAt) {
518
+ return { status: "uncertain", quarantine: "caller" };
519
+ }
520
+ const evidence = safeEvidence(options.evidenceDigest, "attachment-handoff", {
521
+ operationId: request.operationId,
522
+ requestDigest: request.requestDigest,
523
+ actionId: request.actionId,
524
+ targetBindingDigest: request.targetBindingDigest,
525
+ count: request.manifest.count,
526
+ identityDigests: request.manifest.identities.map(entry => entry.identityDigest),
527
+ status: "satisfied"
528
+ });
529
+ return evidence === undefined
530
+ ? { status: "uncertain", quarantine: "provider" }
531
+ : { status: "satisfied", evidenceDigest: evidence };
532
+ }
533
+ function startChooserWait(page, timeoutMs, signal) {
534
+ const wait = {
535
+ promise: Promise.resolve({ kind: "rejected" })
536
+ };
537
+ if (signal?.aborted) {
538
+ wait.outcome = { kind: "aborted" };
539
+ wait.promise = Promise.resolve(wait.outcome);
540
+ return wait;
541
+ }
542
+ const waitForEvent = safeMethod(page, "waitForEvent");
543
+ if (waitForEvent === undefined) {
544
+ wait.outcome = { kind: "rejected" };
545
+ wait.promise = Promise.resolve(wait.outcome);
546
+ return wait;
547
+ }
548
+ let raw;
549
+ try {
550
+ // Registration is synchronous and happens before any provider activation.
551
+ raw = waitForEvent.call(page, "filechooser", { timeout: timeoutMs, timeoutMs });
552
+ }
553
+ catch {
554
+ wait.outcome = { kind: "rejected" };
555
+ wait.promise = Promise.resolve(wait.outcome);
556
+ return wait;
557
+ }
558
+ if (!isNativePromise(raw)) {
559
+ wait.outcome = { kind: "rejected" };
560
+ wait.promise = Promise.resolve(wait.outcome);
561
+ return wait;
562
+ }
563
+ const registration = coordinatedEventRegistrationBarrier(raw);
564
+ if (registration !== undefined)
565
+ wait.registration = registration;
566
+ const handled = raw.then(value => {
567
+ const outcome = !isSafeProviderObject(value) || safeMethod(value, "setFiles") === undefined
568
+ ? { kind: "rejected" }
569
+ : { kind: "success", chooser: value };
570
+ wait.outcome = outcome;
571
+ return outcome;
572
+ }, () => {
573
+ const outcome = { kind: "rejected" };
574
+ wait.outcome = outcome;
575
+ return outcome;
576
+ });
577
+ wait.promise = handled;
578
+ return wait;
579
+ }
580
+ /**
581
+ * Fence a coordinated waitForEvent registration before a browser mutation.
582
+ * The additional host turn catches an already-scheduled provider rejection or
583
+ * stale chooser; neither can have been caused by the click that has not run.
584
+ */
585
+ async function settleChooserBeforeMutation(waiter, deadlineAt, signal) {
586
+ if (waiter.outcome !== undefined)
587
+ return waiter.outcome;
588
+ if (signal?.aborted)
589
+ return { kind: "aborted" };
590
+ if (waiter.registration !== undefined) {
591
+ const budget = remainingBudget(deadlineAt);
592
+ if (budget <= 0)
593
+ return { kind: "timeout" };
594
+ const registration = await awaitRegistration(waiter.registration, budget, signal);
595
+ if (registration === "aborted")
596
+ return { kind: "aborted" };
597
+ if (registration === "timeout")
598
+ return { kind: "timeout" };
599
+ if (waiter.outcome !== undefined)
600
+ return waiter.outcome;
601
+ if (remainingBudget(deadlineAt) <= 0)
602
+ return { kind: "timeout" };
603
+ await new Promise(resolve => setTimeout(resolve, 0));
604
+ }
605
+ else {
606
+ // An ordinary PageLike invokes waitForEvent in this stack. Native promise
607
+ // adoption needs only microtasks; avoid imposing a timer turn on a caller
608
+ // whose deliberately tiny provider timeout has already armed the waiter.
609
+ await flushMicrotasks();
610
+ }
611
+ if (signal?.aborted)
612
+ return { kind: "aborted" };
613
+ if (waiter.outcome !== undefined)
614
+ return waiter.outcome;
615
+ return remainingBudget(deadlineAt) <= 0 ? { kind: "timeout" } : undefined;
616
+ }
617
+ async function awaitRegistration(value, timeoutMs, signal) {
618
+ return await new Promise(resolve => {
619
+ let settled = false;
620
+ const timer = setTimeout(() => finish("timeout"), timeoutMs);
621
+ const onAbort = () => finish("aborted");
622
+ const finish = (result) => {
623
+ if (settled)
624
+ return;
625
+ settled = true;
626
+ clearTimeout(timer);
627
+ signal?.removeEventListener("abort", onAbort);
628
+ resolve(result);
629
+ };
630
+ signal?.addEventListener("abort", onAbort, { once: true });
631
+ if (signal?.aborted) {
632
+ finish("aborted");
633
+ return;
634
+ }
635
+ void value.then(() => finish("settled"), () => finish("timeout"));
636
+ });
637
+ }
638
+ async function awaitChooser(waiter, timeoutMs, signal) {
639
+ if (waiter.outcome !== undefined)
640
+ return waiter.outcome;
641
+ return await new Promise(resolve => {
642
+ let settled = false;
643
+ const onAbort = () => finish({ kind: "aborted" });
644
+ const finish = (outcome) => {
645
+ if (settled)
646
+ return;
647
+ settled = true;
648
+ clearTimeout(timer);
649
+ signal?.removeEventListener("abort", onAbort);
650
+ resolve(outcome);
651
+ };
652
+ const timer = setTimeout(() => finish({ kind: "timeout" }), timeoutMs);
653
+ if (signal !== undefined) {
654
+ signal.addEventListener("abort", onAbort, { once: true });
655
+ if (signal.aborted)
656
+ onAbort();
657
+ }
658
+ void waiter.promise.then(finish, () => finish({ kind: "rejected" }));
659
+ });
660
+ }
661
+ function validateActivation(value, maxCandidates) {
662
+ if (!isPlainDataRecord(value))
663
+ return false;
664
+ const locator = readData(value, "locator");
665
+ const activate = readData(value, "activate");
666
+ const candidateCount = readData(value, "candidateCount");
667
+ const capabilityKey = readData(value, "capabilityKey");
668
+ const locatorActivation = isSafeProviderObject(locator) && activate === undefined;
669
+ const callbackActivation = locator === undefined && typeof activate === "function";
670
+ return (locatorActivation || callbackActivation)
671
+ && Number.isSafeInteger(candidateCount)
672
+ && candidateCount === 1
673
+ && candidateCount <= maxCandidates
674
+ && typeof capabilityKey === "string"
675
+ && CAPABILITY_KEY_PATTERN.test(capabilityKey);
676
+ }
677
+ async function readLocatorCount(locator, timeoutMs) {
678
+ const count = safeMethod(locator, "count");
679
+ if (count === undefined)
680
+ return undefined;
681
+ try {
682
+ const value = await boundedCallback(count.call(locator), timeoutMs);
683
+ return isNonnegativeSafeInteger(value) && value <= MAX_CANDIDATES ? value : undefined;
684
+ }
685
+ catch {
686
+ return undefined;
687
+ }
688
+ }
689
+ async function readLocatorVisible(locator, timeoutMs) {
690
+ const visible = safeMethod(locator, "isVisible");
691
+ if (visible === undefined)
692
+ return undefined;
693
+ try {
694
+ const value = await boundedCallback(visible.call(locator), timeoutMs);
695
+ return typeof value === "boolean" ? value : undefined;
696
+ }
697
+ catch {
698
+ return undefined;
699
+ }
700
+ }
701
+ async function boundedCallback(value, timeoutMs) {
702
+ // The browser contracts use native promises. Reject thenable proxies rather
703
+ // than reading a hostile `then` accessor through Promise.resolve().
704
+ if (isObjectLike(value) && !isNativePromise(value))
705
+ throw new Error("provider promise is not native");
706
+ if (!isNativePromise(value))
707
+ return value;
708
+ let timer;
709
+ const promise = new Promise((resolve, reject) => {
710
+ timer = setTimeout(() => reject(new Error("provider callback timed out")), timeoutMs);
711
+ value.then(resolve, reject);
712
+ });
713
+ try {
714
+ return await promise;
715
+ }
716
+ finally {
717
+ if (timer !== undefined)
718
+ clearTimeout(timer);
719
+ }
720
+ }
721
+ /**
722
+ * Mutating provider calls are deliberately not raced with a local timer.
723
+ * Browser bridges can deliver the click/setFiles action and reject or settle
724
+ * late; returning early would release a coordinated actor while that mutation
725
+ * is still able to land. The provider receives the timeout option, and the
726
+ * outer coordinator owns the transaction deadline/quarantine.
727
+ */
728
+ async function awaitMutatingCallback(value) {
729
+ if (isObjectLike(value) && !isNativePromise(value))
730
+ throw new Error("provider promise is not native");
731
+ if (isNativePromise(value))
732
+ return await value;
733
+ return value;
734
+ }
735
+ function isNativePromise(value) {
736
+ try {
737
+ return nodeTypes.isPromise(value);
738
+ }
739
+ catch {
740
+ return false;
741
+ }
742
+ }
743
+ function sameIdentityList(left, right) {
744
+ let normalized;
745
+ try {
746
+ normalized = cloneIdentityList(left);
747
+ }
748
+ catch {
749
+ return false;
750
+ }
751
+ if (normalized.length !== right.length)
752
+ return false;
753
+ for (let index = 0; index < right.length; index += 1) {
754
+ const candidate = normalized[index];
755
+ const expected = right[index];
756
+ if (candidate === undefined || expected === undefined || !sameIdentity(candidate, expected))
757
+ return false;
758
+ }
759
+ return true;
760
+ }
761
+ function sameIdentity(left, right) {
762
+ return left.sourcePath === right.sourcePath
763
+ && left.manifest.displayName === right.manifest.displayName
764
+ && left.manifest.bytes === right.manifest.bytes
765
+ && left.manifest.contentSha256 === right.manifest.contentSha256
766
+ && left.proof.device === right.proof.device
767
+ && left.proof.inode === right.proof.inode
768
+ && left.proof.size === right.proof.size
769
+ && left.proof.modifiedNs === right.proof.modifiedNs
770
+ && left.proof.changedNs === right.proof.changedNs;
771
+ }
772
+ function cloneIdentityList(value) {
773
+ if (!Array.isArray(value) || value.length > MAX_FILES || !hasSafeArrayDescriptors(value))
774
+ throw new Error("invalid attachment identities");
775
+ const result = [];
776
+ for (let index = 0; index < value.length; index += 1) {
777
+ const identity = value[index];
778
+ if (identity === undefined || !isPlainDataRecord(identity))
779
+ throw new Error("invalid attachment identity");
780
+ const sourcePath = readData(identity, "sourcePath");
781
+ const manifest = cloneManifestEntry(readData(identity, "manifest"));
782
+ const proof = cloneProof(readData(identity, "proof"));
783
+ if (typeof sourcePath !== "string" || sourcePath.length === 0 || sourcePath.length > 4096 || /[\u0000-\u001f\u007f]/u.test(sourcePath) || manifest === undefined || proof === undefined)
784
+ throw new Error("invalid attachment identity");
785
+ result.push(Object.freeze({ sourcePath, manifest, proof }));
786
+ }
787
+ return Object.freeze(result);
788
+ }
789
+ function cloneManifestEntry(value) {
790
+ if (!isPlainDataRecord(value))
791
+ return undefined;
792
+ const displayName = readData(value, "displayName");
793
+ const bytes = readData(value, "bytes");
794
+ const contentSha256 = readData(value, "contentSha256");
795
+ if (typeof displayName !== "string" || displayName.length === 0 || displayName.length > 512 || /[\\/\u0000-\u001f\u007f]/u.test(displayName))
796
+ return undefined;
797
+ if (!isNonnegativeSafeInteger(bytes) || typeof contentSha256 !== "string" || !/^[0-9a-f]{64}$/u.test(contentSha256))
798
+ return undefined;
799
+ return Object.freeze({ displayName, bytes, contentSha256 });
800
+ }
801
+ function cloneProof(value) {
802
+ if (!isPlainDataRecord(value))
803
+ return undefined;
804
+ const device = readData(value, "device");
805
+ const inode = readData(value, "inode");
806
+ const size = readData(value, "size");
807
+ const modifiedNs = readData(value, "modifiedNs");
808
+ const changedNs = readData(value, "changedNs");
809
+ if (typeof device !== "string" || !/^[0-9]+$/u.test(device)
810
+ || typeof inode !== "string" || !/^[0-9]+$/u.test(inode)
811
+ || typeof size !== "string" || !/^[0-9]+$/u.test(size)
812
+ || typeof modifiedNs !== "string" || !/^[0-9]+$/u.test(modifiedNs)
813
+ || typeof changedNs !== "string" || !/^[0-9]+$/u.test(changedNs))
814
+ return undefined;
815
+ return Object.freeze({ device, inode, size, modifiedNs, changedNs });
816
+ }
817
+ function cloneAttachmentRequest(request) {
818
+ return Object.freeze({
819
+ operationId: request.operationId,
820
+ requestDigest: request.requestDigest,
821
+ surface: request.surface,
822
+ targetBindingDigest: request.targetBindingDigest,
823
+ manifest: Object.freeze({
824
+ count: request.manifest.count,
825
+ orderPolicy: "exact",
826
+ identities: Object.freeze(request.manifest.identities.map(entry => Object.freeze({ ...entry })))
827
+ })
828
+ });
829
+ }
830
+ function cloneHandoffRequest(request) {
831
+ return Object.freeze({
832
+ operationId: request.operationId,
833
+ requestDigest: request.requestDigest,
834
+ surface: request.surface,
835
+ actionId: request.actionId,
836
+ targetBindingDigest: request.targetBindingDigest,
837
+ manifest: Object.freeze({
838
+ count: request.manifest.count,
839
+ orderPolicy: "exact",
840
+ identities: Object.freeze(request.manifest.identities.map(entry => Object.freeze({ ...entry })))
841
+ }),
842
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
843
+ ...(request.deadlineAt === undefined ? {} : { deadlineAt: request.deadlineAt })
844
+ });
845
+ }
846
+ function safeEvidence(evidenceDigest, domain, material) {
847
+ try {
848
+ const result = evidenceDigest(domain, material);
849
+ return isDigest(result) ? result : undefined;
850
+ }
851
+ catch {
852
+ return undefined;
853
+ }
854
+ }
855
+ function isDigest(value) {
856
+ return typeof value === "string" && DIGEST_PATTERN.test(value);
857
+ }
858
+ function remainingBudget(deadlineAt) {
859
+ return Math.max(0, Math.min(MAX_TIMEOUT_MS, deadlineAt - Date.now()));
860
+ }
861
+ /**
862
+ * A handoff request is issued only after its durable intent is recorded. A
863
+ * request-local abort therefore cannot authorize a retry, even when it arrives
864
+ * before the provider reaches click/setFiles. Return an explicit quarantine
865
+ * result so callers know to observe the live surface instead.
866
+ */
867
+ function handoffCancellation(signal, deadlineAt) {
868
+ if (signal?.aborted)
869
+ return { status: "uncertain", quarantine: "caller" };
870
+ if (deadlineAt !== undefined && Date.now() >= deadlineAt) {
871
+ return { status: "uncertain", quarantine: "caller" };
872
+ }
873
+ return undefined;
874
+ }
875
+ function isAbortSignal(value) {
876
+ if (typeof AbortSignal === "undefined" || !(value instanceof AbortSignal))
877
+ return false;
878
+ try {
879
+ return typeof value.aborted === "boolean"
880
+ && typeof value.addEventListener === "function"
881
+ && typeof value.removeEventListener === "function";
882
+ }
883
+ catch {
884
+ return false;
885
+ }
886
+ }
887
+ async function flushMicrotasks() {
888
+ await Promise.resolve();
889
+ await Promise.resolve();
890
+ }
891
+ function readData(value, key) {
892
+ try {
893
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
894
+ if (descriptor === undefined || !("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined)
895
+ return undefined;
896
+ return descriptor.value;
897
+ }
898
+ catch {
899
+ return undefined;
900
+ }
901
+ }
902
+ function safeMethod(value, key) {
903
+ let current = value;
904
+ for (let depth = 0; current !== null && depth < MAX_GRAPH_DEPTH; depth += 1) {
905
+ const descriptor = readDescriptor(current, key);
906
+ if (descriptor !== undefined) {
907
+ if (!("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined || typeof descriptor.value !== "function")
908
+ return undefined;
909
+ return descriptor.value;
910
+ }
911
+ current = readPrototype(current);
912
+ }
913
+ return undefined;
914
+ }
915
+ function providerCallable(value, key) {
916
+ try {
917
+ const candidate = Reflect.get(value, key, value);
918
+ if (typeof candidate !== "function")
919
+ return undefined;
920
+ return (...args) => Reflect.apply(candidate, value, args);
921
+ }
922
+ catch {
923
+ return undefined;
924
+ }
925
+ }
926
+ function readDescriptor(value, key) {
927
+ try {
928
+ return Object.getOwnPropertyDescriptor(value, key);
929
+ }
930
+ catch {
931
+ return undefined;
932
+ }
933
+ }
934
+ function readPrototype(value) {
935
+ try {
936
+ return Object.getPrototypeOf(value);
937
+ }
938
+ catch {
939
+ return null;
940
+ }
941
+ }
942
+ function isSafeProviderObject(value) {
943
+ if (!isObjectLike(value))
944
+ return false;
945
+ // A descriptor walk is intentionally shallow for browser objects; provider
946
+ // methods may live on prototypes and are validated at each invocation.
947
+ try {
948
+ const descriptors = Object.getOwnPropertyDescriptors(value);
949
+ for (const key of Reflect.ownKeys(descriptors)) {
950
+ if (typeof key !== "string")
951
+ return false;
952
+ const descriptor = descriptors[key];
953
+ if (descriptor === undefined || descriptor.get !== undefined || descriptor.set !== undefined)
954
+ return false;
955
+ }
956
+ return true;
957
+ }
958
+ catch {
959
+ return false;
960
+ }
961
+ }
962
+ function isSafeDataGraph(value, seen = new Set(), depth = 0) {
963
+ if (value === null || typeof value !== "object")
964
+ return value !== undefined && typeof value !== "function";
965
+ if (depth > MAX_GRAPH_DEPTH || seen.has(value))
966
+ return depth <= MAX_GRAPH_DEPTH;
967
+ seen.add(value);
968
+ if (seen.size > MAX_GRAPH_NODES)
969
+ return false;
970
+ let prototype;
971
+ let descriptors;
972
+ try {
973
+ prototype = Object.getPrototypeOf(value);
974
+ descriptors = Object.getOwnPropertyDescriptors(value);
975
+ }
976
+ catch {
977
+ return false;
978
+ }
979
+ if (prototype !== Object.prototype && prototype !== null && !Array.isArray(value))
980
+ return false;
981
+ for (const key of Reflect.ownKeys(descriptors)) {
982
+ if (typeof key !== "string")
983
+ return false;
984
+ const descriptor = descriptors[key];
985
+ if (descriptor === undefined || !("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined)
986
+ return false;
987
+ if (!isSafeDataGraph(descriptor.value, seen, depth + 1))
988
+ return false;
989
+ }
990
+ return true;
991
+ }
992
+ function isObjectLike(value) {
993
+ return value !== null && (typeof value === "object" || typeof value === "function");
994
+ }
995
+ function hasSafeArrayDescriptors(value) {
996
+ try {
997
+ const descriptors = Object.getOwnPropertyDescriptors(value);
998
+ for (const key of Reflect.ownKeys(descriptors)) {
999
+ if (typeof key !== "string")
1000
+ return false;
1001
+ const descriptor = descriptors[key];
1002
+ if (descriptor === undefined || !("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined)
1003
+ return false;
1004
+ }
1005
+ const lengthDescriptor = readDescriptor(value, "length");
1006
+ const length = lengthDescriptor !== undefined && "value" in lengthDescriptor ? lengthDescriptor.value : undefined;
1007
+ return isNonnegativeSafeInteger(length) && length === value.length;
1008
+ }
1009
+ catch {
1010
+ return false;
1011
+ }
1012
+ }
1013
+ function isNonnegativeSafeInteger(value) {
1014
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1015
+ }
1016
+ function isPositiveSafeInteger(value) {
1017
+ return isNonnegativeSafeInteger(value) && value > 0;
1018
+ }