@nuanu-ai/agentbrowse 0.2.46 → 0.2.48

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 (190) hide show
  1. package/README.md +69 -10
  2. package/dist/agentpay-gateway.d.ts +9 -0
  3. package/dist/agentpay-gateway.d.ts.map +1 -1
  4. package/dist/agentpay-gateway.js +30 -0
  5. package/dist/agentpay-stagehand-llm.d.ts.map +1 -1
  6. package/dist/agentpay-stagehand-llm.js +9 -1
  7. package/dist/command-api-tracing.d.ts +19 -0
  8. package/dist/command-api-tracing.d.ts.map +1 -0
  9. package/dist/command-api-tracing.js +137 -0
  10. package/dist/commands/act.d.ts.map +1 -1
  11. package/dist/commands/act.js +822 -670
  12. package/dist/commands/act.test-harness.d.ts +6 -0
  13. package/dist/commands/act.test-harness.d.ts.map +1 -1
  14. package/dist/commands/act.test-harness.js +44 -1
  15. package/dist/commands/action-acceptance.d.ts.map +1 -1
  16. package/dist/commands/action-acceptance.js +115 -0
  17. package/dist/commands/captcha-solve.d.ts.map +1 -1
  18. package/dist/commands/captcha-solve.js +83 -16
  19. package/dist/commands/click-action-executor.d.ts +0 -1
  20. package/dist/commands/click-action-executor.d.ts.map +1 -1
  21. package/dist/commands/click-action-executor.js +31 -77
  22. package/dist/commands/close.d.ts +3 -3
  23. package/dist/commands/close.d.ts.map +1 -1
  24. package/dist/commands/close.js +178 -0
  25. package/dist/commands/descriptor-validation.d.ts.map +1 -1
  26. package/dist/commands/descriptor-validation.js +75 -57
  27. package/dist/commands/end-session.d.ts +25 -0
  28. package/dist/commands/end-session.d.ts.map +1 -0
  29. package/dist/commands/end-session.js +161 -0
  30. package/dist/commands/extract-stagehand-executor.js +1 -1
  31. package/dist/commands/extract.d.ts.map +1 -1
  32. package/dist/commands/extract.js +339 -202
  33. package/dist/commands/fill-secret.d.ts +3 -3
  34. package/dist/commands/fill-secret.d.ts.map +1 -1
  35. package/dist/commands/fill-secret.js +419 -234
  36. package/dist/commands/get-secrets-catalog.d.ts.map +1 -1
  37. package/dist/commands/get-secrets-catalog.js +66 -5
  38. package/dist/commands/interaction-kernel.d.ts +46 -0
  39. package/dist/commands/interaction-kernel.d.ts.map +1 -0
  40. package/dist/commands/interaction-kernel.js +215 -0
  41. package/dist/commands/launch.d.ts +0 -2
  42. package/dist/commands/launch.d.ts.map +1 -1
  43. package/dist/commands/launch.js +109 -17
  44. package/dist/commands/navigate.d.ts.map +1 -1
  45. package/dist/commands/navigate.js +188 -45
  46. package/dist/commands/observe-accessibility.d.ts.map +1 -1
  47. package/dist/commands/observe-accessibility.js +50 -39
  48. package/dist/commands/observe-dom-label-contract.d.ts.map +1 -1
  49. package/dist/commands/observe-dom-label-contract.js +5 -0
  50. package/dist/commands/observe-inventory.d.ts +13 -0
  51. package/dist/commands/observe-inventory.d.ts.map +1 -1
  52. package/dist/commands/observe-inventory.js +320 -65
  53. package/dist/commands/observe-persistence.d.ts.map +1 -1
  54. package/dist/commands/observe-persistence.js +3 -0
  55. package/dist/commands/observe-projection.d.ts +1 -0
  56. package/dist/commands/observe-projection.d.ts.map +1 -1
  57. package/dist/commands/observe-projection.js +7 -2
  58. package/dist/commands/observe-protected.d.ts +1 -0
  59. package/dist/commands/observe-protected.d.ts.map +1 -1
  60. package/dist/commands/observe-protected.js +9 -4
  61. package/dist/commands/observe-semantics.d.ts.map +1 -1
  62. package/dist/commands/observe-semantics.js +5 -2
  63. package/dist/commands/observe-stagehand.d.ts +1 -0
  64. package/dist/commands/observe-stagehand.d.ts.map +1 -1
  65. package/dist/commands/observe-stagehand.js +2 -0
  66. package/dist/commands/observe.d.ts +2 -0
  67. package/dist/commands/observe.d.ts.map +1 -1
  68. package/dist/commands/observe.js +387 -203
  69. package/dist/commands/observe.test-harness.d.ts +8 -0
  70. package/dist/commands/observe.test-harness.d.ts.map +1 -1
  71. package/dist/commands/observe.test-harness.js +48 -1
  72. package/dist/commands/poll-secret.d.ts +6 -0
  73. package/dist/commands/poll-secret.d.ts.map +1 -0
  74. package/dist/commands/poll-secret.js +159 -0
  75. package/dist/commands/request-secret.d.ts +6 -0
  76. package/dist/commands/request-secret.d.ts.map +1 -0
  77. package/dist/commands/request-secret.js +284 -0
  78. package/dist/commands/screenshot.d.ts.map +1 -1
  79. package/dist/commands/screenshot.js +172 -7
  80. package/dist/commands/select-action-executor.d.ts.map +1 -1
  81. package/dist/commands/semantic-observe.d.ts +4 -0
  82. package/dist/commands/semantic-observe.d.ts.map +1 -1
  83. package/dist/commands/semantic-observe.js +388 -17
  84. package/dist/commands/start-session.d.ts +31 -0
  85. package/dist/commands/start-session.d.ts.map +1 -0
  86. package/dist/commands/start-session.js +347 -0
  87. package/dist/commands/status.d.ts +2 -1
  88. package/dist/commands/status.d.ts.map +1 -1
  89. package/dist/commands/status.js +166 -144
  90. package/dist/control-semantics.d.ts +1 -0
  91. package/dist/control-semantics.d.ts.map +1 -1
  92. package/dist/control-semantics.js +51 -9
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +144 -45
  95. package/dist/otel-exporter.d.ts +58 -0
  96. package/dist/otel-exporter.d.ts.map +1 -0
  97. package/dist/otel-exporter.js +263 -0
  98. package/dist/otel-projector.d.ts +75 -0
  99. package/dist/otel-projector.d.ts.map +1 -0
  100. package/dist/otel-projector.js +409 -0
  101. package/dist/owned-browser.d.ts +1 -1
  102. package/dist/owned-browser.d.ts.map +1 -1
  103. package/dist/owned-browser.js +13 -1
  104. package/dist/owned-process.d.ts +2 -0
  105. package/dist/owned-process.d.ts.map +1 -1
  106. package/dist/owned-process.js +7 -3
  107. package/dist/playwright-runtime.d.ts +1 -1
  108. package/dist/playwright-runtime.d.ts.map +1 -1
  109. package/dist/playwright-runtime.js +8 -8
  110. package/dist/run-observability.d.ts +25 -0
  111. package/dist/run-observability.d.ts.map +1 -0
  112. package/dist/run-observability.js +115 -0
  113. package/dist/run-store.d.ts +274 -0
  114. package/dist/run-store.d.ts.map +1 -0
  115. package/dist/run-store.js +631 -0
  116. package/dist/runtime-metrics.d.ts +27 -0
  117. package/dist/runtime-metrics.d.ts.map +1 -0
  118. package/dist/runtime-metrics.js +66 -0
  119. package/dist/runtime-page-state.d.ts +11 -0
  120. package/dist/runtime-page-state.d.ts.map +1 -0
  121. package/dist/runtime-page-state.js +62 -0
  122. package/dist/runtime-protected-state.d.ts +16 -0
  123. package/dist/runtime-protected-state.d.ts.map +1 -0
  124. package/dist/runtime-protected-state.js +157 -0
  125. package/dist/runtime-state.d.ts +10 -44
  126. package/dist/runtime-state.d.ts.map +1 -1
  127. package/dist/runtime-state.js +57 -222
  128. package/dist/secrets/backend.d.ts +65 -16
  129. package/dist/secrets/backend.d.ts.map +1 -1
  130. package/dist/secrets/backend.js +135 -95
  131. package/dist/secrets/catalog-sync.d.ts.map +1 -1
  132. package/dist/secrets/catalog-sync.js +4 -1
  133. package/dist/secrets/form-matcher.d.ts +5 -5
  134. package/dist/secrets/form-matcher.d.ts.map +1 -1
  135. package/dist/secrets/form-matcher.js +292 -164
  136. package/dist/secrets/intent-output.d.ts +6 -10
  137. package/dist/secrets/intent-output.d.ts.map +1 -1
  138. package/dist/secrets/intent-output.js +4 -58
  139. package/dist/secrets/mock-agentpay-cabinet.d.ts +38 -27
  140. package/dist/secrets/mock-agentpay-cabinet.d.ts.map +1 -1
  141. package/dist/secrets/mock-agentpay-cabinet.js +177 -111
  142. package/dist/secrets/protected-artifact-guard.d.ts +2 -2
  143. package/dist/secrets/protected-artifact-guard.d.ts.map +1 -1
  144. package/dist/secrets/protected-artifact-guard.js +2 -2
  145. package/dist/secrets/protected-bindings.d.ts +1 -1
  146. package/dist/secrets/protected-bindings.d.ts.map +1 -1
  147. package/dist/secrets/protected-bindings.js +6 -0
  148. package/dist/secrets/protected-field-semantics.d.ts +9 -0
  149. package/dist/secrets/protected-field-semantics.d.ts.map +1 -0
  150. package/dist/secrets/protected-field-semantics.js +154 -0
  151. package/dist/secrets/protected-field-values.d.ts.map +1 -1
  152. package/dist/secrets/protected-field-values.js +3 -3
  153. package/dist/secrets/protected-fill.d.ts +1 -1
  154. package/dist/secrets/protected-fill.d.ts.map +1 -1
  155. package/dist/secrets/protected-fill.js +45 -149
  156. package/dist/secrets/protected-value-adapters.d.ts +2 -1
  157. package/dist/secrets/protected-value-adapters.d.ts.map +1 -1
  158. package/dist/secrets/protected-value-adapters.js +80 -1
  159. package/dist/secrets/request-output.d.ts +11 -0
  160. package/dist/secrets/request-output.d.ts.map +1 -0
  161. package/dist/secrets/request-output.js +75 -0
  162. package/dist/secrets/types.d.ts +15 -9
  163. package/dist/secrets/types.d.ts.map +1 -1
  164. package/dist/secrets/types.js +3 -0
  165. package/dist/session-event-exporter.d.ts +36 -0
  166. package/dist/session-event-exporter.d.ts.map +1 -0
  167. package/dist/session-event-exporter.js +428 -0
  168. package/dist/session.d.ts +16 -7
  169. package/dist/session.d.ts.map +1 -1
  170. package/dist/session.js +150 -23
  171. package/dist/sessions-backend.d.ts +354 -0
  172. package/dist/sessions-backend.d.ts.map +1 -0
  173. package/dist/sessions-backend.js +126 -0
  174. package/dist/solver/browser-launcher.d.ts +1 -1
  175. package/dist/solver/browser-launcher.d.ts.map +1 -1
  176. package/dist/solver/browser-launcher.js +39 -13
  177. package/dist/solver/captcha-solver.d.ts.map +1 -1
  178. package/dist/solver/captcha-solver.js +8 -1
  179. package/dist/solver/types.d.ts +1 -0
  180. package/dist/solver/types.d.ts.map +1 -1
  181. package/dist/workflow-session-completion.d.ts +33 -0
  182. package/dist/workflow-session-completion.d.ts.map +1 -0
  183. package/dist/workflow-session-completion.js +156 -0
  184. package/package.json +9 -1
  185. package/dist/commands/create-intent.d.ts +0 -6
  186. package/dist/commands/create-intent.d.ts.map +0 -1
  187. package/dist/commands/create-intent.js +0 -75
  188. package/dist/commands/poll-intent.d.ts +0 -6
  189. package/dist/commands/poll-intent.d.ts.map +0 -1
  190. package/dist/commands/poll-intent.js +0 -57
@@ -1,39 +1,45 @@
1
1
  /**
2
- * browse fill-secret <fillRef> <intentId> — Fill protected fields from a cached one-time secret payload
3
- * using the persisted plan and deterministic browser execution.
2
+ * browse fill-secret <fillRef> <requestId> — Claim an already approved secret request and fill
3
+ * protected fields using deterministic browser execution.
4
4
  */
5
5
  import { outputFailure, outputJSON } from '../output.js';
6
- import { getFillableForm, getSecretIntentSnapshot, saveProtectedExposure, saveSecretIntentSnapshot, } from '../runtime-state.js';
7
- import { deleteCachedTransientSecret, getCachedTransientSecret, saveSession } from '../session.js';
6
+ import { getFillableForm, getSecretRequestSnapshot, saveProtectedExposure, saveSecretRequestSnapshot, } from '../runtime-protected-state.js';
8
7
  import { connectPlaywright, disconnectPlaywright, resolvePageByRef, syncSessionPage, } from '../playwright-runtime.js';
8
+ import { finishRunStep, startRunStep } from '../run-store.js';
9
+ import { exportRunStepToOtlpHttpJsonBestEffort } from '../otel-exporter.js';
10
+ import { appendCommandLifecycleEventBestEffort, captureStepSnapshotBestEffort, } from '../run-observability.js';
11
+ import { saveSession } from '../session.js';
9
12
  import { tryResolveCatalogHost } from '../secrets/catalog-sync.js';
10
- import { describeSecretIntentStatus, serializeSecretIntentContext, } from '../secrets/intent-output.js';
13
+ import { describeSecretRequestStatus, serializeSecretRequestContext, } from '../secrets/request-output.js';
11
14
  import { executeProtectedFill } from '../secrets/protected-fill.js';
12
- function persistIntentSnapshot(session, snapshot) {
13
- const persisted = saveSecretIntentSnapshot(session, snapshot);
15
+ import { getSecretBackend } from '../secrets/backend.js';
16
+ import { AgentpayRequestError } from '../sessions-backend.js';
17
+ function persistRequestSnapshot(session, snapshot) {
18
+ const persisted = saveSecretRequestSnapshot(session, snapshot);
14
19
  saveSession(session);
15
20
  return persisted;
16
21
  }
17
- function completeIntent(session, intentId) {
18
- const current = getSecretIntentSnapshot(session, intentId);
22
+ function markRequestClaimed(session, requestId, claimedAt) {
23
+ const current = getSecretRequestSnapshot(session, requestId);
19
24
  if (!current) {
20
- throw new Error('secret_intent_not_found');
25
+ throw new Error('secret_request_not_found');
21
26
  }
22
- if (current.status === 'completed') {
23
- return current;
27
+ if (session.currentRequestId === requestId) {
28
+ session.currentRequestId = undefined;
29
+ session.lastKnownStatus = 'in_progress';
24
30
  }
25
- return persistIntentSnapshot(session, {
31
+ return persistRequestSnapshot(session, {
26
32
  ...current,
27
- status: 'completed',
28
- completedAt: new Date().toISOString(),
33
+ claimedAt,
34
+ updatedAt: claimedAt,
29
35
  });
30
36
  }
31
- function activateProtectedExposure(session, fillableForm, intentId, reason) {
37
+ function activateProtectedExposure(session, fillableForm, requestId, reason) {
32
38
  saveProtectedExposure(session, {
33
39
  pageRef: fillableForm.pageRef,
34
40
  scopeRef: fillableForm.scopeRef,
35
41
  fillRef: fillableForm.fillRef,
36
- intentId,
42
+ requestId: requestId,
37
43
  activatedAt: new Date().toISOString(),
38
44
  reason,
39
45
  });
@@ -53,319 +59,498 @@ function restrictFormToCandidateFields(fillableForm, storedSecretCandidate) {
53
59
  fields: filteredFields,
54
60
  };
55
61
  }
56
- function nextActionForNonApprovedIntent(status) {
57
- switch (status) {
58
- case 'pending':
59
- case 'checking':
60
- case 'notify':
61
- case 'confirmation':
62
- return 'wait-for-approval';
63
- case 'denied':
64
- return 'ask-user';
65
- case 'timed_out':
66
- case 'completed':
67
- return 'ask-user';
68
- default:
69
- return 'ask-user';
70
- }
62
+ function withFillableFormPresence(fillableForm, payload) {
63
+ return {
64
+ ...payload,
65
+ fillableFormPresence: fillableForm.presence ?? 'present',
66
+ };
71
67
  }
72
- function messageForNonApprovedIntent(status) {
73
- switch (status) {
74
- case 'pending':
75
- case 'checking':
76
- case 'notify':
77
- case 'confirmation':
78
- return 'Protected fill is waiting for user approval in AgentPay Cabinet.';
79
- case 'denied':
80
- return 'Protected fill is blocked because the user denied this intent in AgentPay Cabinet.';
81
- case 'timed_out':
82
- case 'completed':
83
- return 'Protected fill requires a new intent because this intent is no longer usable.';
84
- default:
85
- return 'Protected fill cannot continue because the intent is not approved.';
68
+ function finalizeFillSecretStepBestEffort(runId, stepId, options) {
69
+ if (!runId || !stepId) {
70
+ return;
86
71
  }
72
+ try {
73
+ finishRunStep({
74
+ runId,
75
+ stepId,
76
+ ...options,
77
+ });
78
+ }
79
+ catch {
80
+ }
81
+ }
82
+ async function emitFillSecretFailure(session, runId, stepId, payload, options = {}) {
83
+ const step = runId && stepId ? { runId, stepId, command: 'fill-secret' } : null;
84
+ captureStepSnapshotBestEffort({
85
+ session,
86
+ step,
87
+ phase: 'point-in-time',
88
+ pageRef: options.pageRef ?? session.runtime?.currentPageRef,
89
+ });
90
+ appendCommandLifecycleEventBestEffort({
91
+ step,
92
+ phase: 'failed',
93
+ attributes: {
94
+ outcomeType: typeof payload.outcomeType === 'string' ? payload.outcomeType : payload.error,
95
+ pageRef: options.pageRef,
96
+ fillRef: options.fillRef,
97
+ requestId: options.requestId,
98
+ reason: typeof payload.reason === 'string' ? payload.reason : payload.error,
99
+ },
100
+ });
101
+ finalizeFillSecretStepBestEffort(runId, stepId, {
102
+ success: false,
103
+ outcomeType: typeof payload.outcomeType === 'string' ? payload.outcomeType : payload.error,
104
+ message: typeof payload.message === 'string' ? payload.message : payload.error,
105
+ reason: typeof payload.reason === 'string' ? payload.reason : payload.error,
106
+ });
107
+ await exportRunStepToOtlpHttpJsonBestEffort(runId, stepId);
108
+ return outputFailure(payload);
87
109
  }
88
- function outcomeTypeForNonApprovedIntent(status) {
89
- return describeSecretIntentStatus(status).outcomeType;
110
+ async function emitFillSecretSuccess(session, runId, stepId, payload, options = {}) {
111
+ const step = runId && stepId ? { runId, stepId, command: 'fill-secret' } : null;
112
+ captureStepSnapshotBestEffort({
113
+ session,
114
+ step,
115
+ phase: 'after',
116
+ pageRef: options.pageRef ?? session.runtime?.currentPageRef,
117
+ });
118
+ appendCommandLifecycleEventBestEffort({
119
+ step,
120
+ phase: 'completed',
121
+ attributes: {
122
+ outcomeType: typeof payload.outcomeType === 'string' ? payload.outcomeType : 'protected_fill_completed',
123
+ pageRef: options.pageRef,
124
+ fillRef: options.fillRef,
125
+ requestId: options.requestId,
126
+ },
127
+ });
128
+ finalizeFillSecretStepBestEffort(runId, stepId, {
129
+ success: true,
130
+ outcomeType: typeof payload.outcomeType === 'string' ? payload.outcomeType : 'protected_fill_completed',
131
+ message: typeof payload.message === 'string' ? payload.message : 'Protected fill completed.',
132
+ reason: typeof payload.reason === 'string' ? payload.reason : undefined,
133
+ });
134
+ await exportRunStepToOtlpHttpJsonBestEffort(runId, stepId);
135
+ return outputJSON(payload);
136
+ }
137
+ function parseClaimFailure(error) {
138
+ const message = error instanceof Error ? error.message : String(error);
139
+ if (message.includes('not approved yet') ||
140
+ message.includes('secret_request_not_fulfilled') ||
141
+ message.includes('does not have resolved secret values yet')) {
142
+ return 'not_fulfilled';
143
+ }
144
+ if (message.includes('already claimed') || message.includes('secret_request_already_claimed')) {
145
+ return 'already_claimed';
146
+ }
147
+ return 'other';
90
148
  }
91
- function reasonForNonApprovedIntent(status) {
92
- return describeSecretIntentStatus(status).reason;
149
+ function failureForUnfulfilledRequest(fillableForm, request, fillRef, requestId) {
150
+ const statusContract = describeSecretRequestStatus(request.status, request.requestType);
151
+ return withFillableFormPresence(fillableForm, {
152
+ error: 'secret_request_not_ready',
153
+ ...(statusContract.outcomeType ? { outcomeType: statusContract.outcomeType } : {}),
154
+ message: request.status === 'pending'
155
+ ? 'Protected fill is waiting for user approval in AgentPay.'
156
+ : statusContract.message,
157
+ reason: statusContract.reason,
158
+ fillRef,
159
+ requestId,
160
+ ...serializeSecretRequestContext(request),
161
+ requestStatus: request.status,
162
+ nextAction: statusContract.nextAction ?? 'poll-secret',
163
+ });
93
164
  }
94
- export async function fillSecret(session, fillRef, intentId) {
165
+ export async function fillSecret(session, fillRef, requestId) {
166
+ const fillSecretStep = session.activeRunId
167
+ ? startRunStep({
168
+ runId: session.activeRunId,
169
+ command: 'fill-secret',
170
+ refs: {
171
+ fillRef,
172
+ requestId,
173
+ },
174
+ protectedStep: true,
175
+ })
176
+ : null;
177
+ const fillSecretStepHandle = session.activeRunId && fillSecretStep?.stepId
178
+ ? {
179
+ runId: session.activeRunId,
180
+ stepId: fillSecretStep.stepId,
181
+ command: 'fill-secret',
182
+ }
183
+ : null;
184
+ captureStepSnapshotBestEffort({
185
+ session,
186
+ step: fillSecretStepHandle,
187
+ phase: 'before',
188
+ pageRef: session.runtime?.currentPageRef,
189
+ });
190
+ appendCommandLifecycleEventBestEffort({
191
+ step: fillSecretStepHandle,
192
+ phase: 'started',
193
+ attributes: {
194
+ fillRef,
195
+ requestId,
196
+ pageRef: session.runtime?.currentPageRef,
197
+ },
198
+ });
199
+ if (!session.intentSessionId) {
200
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, {
201
+ error: 'workflow_session_unavailable',
202
+ outcomeType: 'blocked',
203
+ message: 'Protected fill was not started because no active workflow session is bound to this browser.',
204
+ reason: 'Run start-session first so AgentBrowse has a workflow session for secret requests.',
205
+ fillRef,
206
+ requestId,
207
+ }, {
208
+ fillRef,
209
+ requestId,
210
+ });
211
+ }
95
212
  const fillableForm = getFillableForm(session, fillRef);
96
213
  if (!fillableForm) {
97
- return outputFailure({
214
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, {
98
215
  error: 'unknown_fill_ref',
99
216
  outcomeType: 'blocked',
100
217
  message: 'Protected fill was not started because the requested fill reference is unknown.',
101
218
  reason: 'The provided fillRef does not match any currently observed protected fill target.',
102
219
  fillRef,
103
- intentId,
220
+ requestId,
221
+ }, {
222
+ fillRef,
223
+ requestId,
104
224
  });
105
225
  }
106
- const intent = getSecretIntentSnapshot(session, intentId);
107
- if (!intent) {
108
- return outputFailure({
109
- error: 'secret_intent_not_found',
226
+ if (fillableForm.presence === 'absent') {
227
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, withFillableFormPresence(fillableForm, {
228
+ error: 'fillable_form_absent',
110
229
  outcomeType: 'blocked',
111
- message: 'Protected fill was not started because the requested intent does not exist.',
112
- reason: 'The provided intentId was not found in AgentPay Cabinet.',
230
+ message: 'Protected fill was not started because the requested fill target is no longer present on the page.',
231
+ reason: 'AgentBrowse already observed this protected fill target as absent, so a new observe pass is required before protected fill can continue.',
232
+ fillRef,
233
+ requestId,
234
+ }), {
235
+ pageRef: fillableForm.pageRef,
113
236
  fillRef,
114
- intentId,
237
+ requestId,
115
238
  });
116
239
  }
117
- const latestIntent = persistIntentSnapshot(session, intent);
118
- if (latestIntent.fillRef !== fillRef) {
119
- return outputFailure({
120
- error: 'secret_intent_fill_mismatch',
240
+ const request = getSecretRequestSnapshot(session, requestId);
241
+ if (!request) {
242
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, {
243
+ error: 'secret_request_not_found',
121
244
  outcomeType: 'blocked',
122
- message: 'Protected fill was blocked because the approved intent belongs to a different fill target.',
123
- reason: `The approved intent belongs to fillRef ${latestIntent.fillRef}, not ${fillRef}.`,
245
+ message: 'Protected fill was not started because the requested secret request does not exist.',
246
+ reason: 'Run request-secret and poll-secret first so AgentBrowse has the latest request state.',
124
247
  fillRef,
125
- intentId,
126
- ...serializeSecretIntentContext(latestIntent),
127
- intentFillRef: latestIntent.fillRef,
128
- intentStatus: latestIntent.status,
129
- nextAction: 'reobserve',
130
- });
131
- }
132
- if (!(latestIntent.status === 'approved' || latestIntent.status === 'completed')) {
133
- return outputFailure({
134
- error: 'secret_intent_not_approved',
135
- ...(outcomeTypeForNonApprovedIntent(latestIntent.status)
136
- ? { outcomeType: outcomeTypeForNonApprovedIntent(latestIntent.status) }
137
- : {}),
138
- message: messageForNonApprovedIntent(latestIntent.status),
139
- reason: reasonForNonApprovedIntent(latestIntent.status),
248
+ requestId,
249
+ }, {
250
+ pageRef: fillableForm.pageRef,
140
251
  fillRef,
141
- intentId,
142
- ...serializeSecretIntentContext(latestIntent),
143
- intentStatus: latestIntent.status,
144
- nextAction: nextActionForNonApprovedIntent(latestIntent.status),
252
+ requestId,
145
253
  });
146
254
  }
147
- const cachedSecret = getCachedTransientSecret(session, intentId);
148
- if (!cachedSecret) {
149
- const missingCacheReason = latestIntent.status === 'completed'
150
- ? 'This one-time intent has already delivered its protected payload, but the current session no longer has it cached.'
151
- : 'Run poll-intent after approval so AgentBrowse can cache the transient protected values before fill-secret.';
152
- const missingCacheNextAction = latestIntent.status === 'completed' ? 'ask-user' : 'poll-intent';
153
- const missingCacheMessage = latestIntent.status === 'completed'
154
- ? 'Protected fill could not continue because the one-time secret payload is no longer cached in the current session.'
155
- : 'Protected fill could not continue because the approved secret payload is not cached in the current session.';
156
- return outputFailure({
157
- error: 'transient_secret_not_cached',
255
+ const latestRequest = persistRequestSnapshot(session, request);
256
+ if (latestRequest.fillRef !== fillRef) {
257
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, withFillableFormPresence(fillableForm, {
258
+ error: 'secret_request_fill_mismatch',
158
259
  outcomeType: 'blocked',
159
- message: missingCacheMessage,
160
- reason: missingCacheReason,
260
+ message: 'Protected fill was blocked because the approved secret request belongs to a different fill target.',
261
+ reason: `The approved request belongs to fillRef ${latestRequest.fillRef}, not ${fillRef}.`,
262
+ fillRef,
263
+ requestId,
264
+ ...serializeSecretRequestContext(latestRequest),
265
+ requestFillRef: latestRequest.fillRef,
266
+ requestStatus: latestRequest.status,
267
+ nextAction: 'reobserve',
268
+ }), {
269
+ pageRef: fillableForm.pageRef,
161
270
  fillRef,
162
- intentId,
163
- ...serializeSecretIntentContext(latestIntent),
164
- intentStatus: latestIntent.status,
165
- nextAction: missingCacheNextAction,
271
+ requestId,
166
272
  });
167
273
  }
168
- if (cachedSecret.fillRef !== fillRef ||
169
- cachedSecret.storedSecretRef !== latestIntent.storedSecretRef) {
170
- deleteCachedTransientSecret(session, intentId);
171
- saveSession(session);
172
- return outputFailure({
173
- error: 'transient_secret_context_mismatch',
274
+ if (latestRequest.claimedAt) {
275
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, withFillableFormPresence(fillableForm, {
276
+ error: 'secret_request_already_claimed',
174
277
  outcomeType: 'blocked',
175
- message: 'Protected fill was blocked because the cached transient secret no longer matches this approved intent.',
176
- reason: 'The cached transient payload belongs to a different fill target or stored secret reference.',
278
+ message: 'Protected fill could not continue because this one-time secret request was already claimed earlier.',
279
+ reason: 'Secret values can only be claimed once for this request, so AgentBrowse needs a new request before it can fill again.',
280
+ fillRef,
281
+ requestId,
282
+ ...serializeSecretRequestContext(latestRequest),
283
+ requestStatus: latestRequest.status,
284
+ nextAction: 'request-secret',
285
+ }), {
286
+ pageRef: fillableForm.pageRef,
177
287
  fillRef,
178
- intentId,
179
- ...serializeSecretIntentContext(latestIntent),
180
- intentStatus: latestIntent.status,
181
- nextAction: 'poll-intent',
288
+ requestId,
182
289
  });
183
290
  }
184
- let browser = null;
185
- try {
186
- browser = await connectPlaywright(session.cdpUrl);
187
- }
188
- catch (error) {
189
- deleteCachedTransientSecret(session, intentId);
190
- saveSession(session);
191
- return outputFailure({
192
- error: 'browser_connection_failed',
193
- message: 'Protected fill could not connect to the browser.',
291
+ if (latestRequest.status !== 'fulfilled') {
292
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, failureForUnfulfilledRequest(fillableForm, latestRequest, fillRef, requestId), {
293
+ pageRef: fillableForm.pageRef,
194
294
  fillRef,
195
- intentId,
196
- ...serializeSecretIntentContext(latestIntent),
197
- reason: error instanceof Error ? error.message : String(error),
295
+ requestId,
198
296
  });
199
297
  }
200
- const connectedBrowser = browser;
298
+ let browser = null;
201
299
  let finalResult = null;
202
300
  try {
203
- const page = await resolvePageByRef(connectedBrowser, session, fillableForm.pageRef);
301
+ browser = await connectPlaywright(session.cdpUrl);
302
+ const page = await resolvePageByRef(browser, session, fillableForm.pageRef);
204
303
  const syncedPage = await syncSessionPage(session, fillableForm.pageRef, page);
205
304
  const observedHost = tryResolveCatalogHost(syncedPage.url);
206
305
  const contextMismatchReasons = [];
207
- if (latestIntent.host && observedHost && latestIntent.host !== observedHost) {
306
+ if (latestRequest.host && observedHost && latestRequest.host !== observedHost) {
208
307
  contextMismatchReasons.push('host');
209
308
  }
210
- if (latestIntent.scopeRef && latestIntent.scopeRef !== fillableForm.scopeRef) {
309
+ if (latestRequest.scopeRef && latestRequest.scopeRef !== fillableForm.scopeRef) {
211
310
  contextMismatchReasons.push('scope');
212
311
  }
213
312
  if (contextMismatchReasons.length > 0) {
214
- const completedIntent = completeIntent(session, intentId);
215
313
  finalResult = {
216
314
  success: false,
217
- payload: {
218
- error: 'secret_intent_context_mismatch',
315
+ payload: withFillableFormPresence(fillableForm, {
316
+ error: 'secret_request_context_mismatch',
219
317
  outcomeType: 'blocked',
220
- message: 'Protected fill was blocked because the page or protected surface no longer matches the approved intent context.',
221
- reason: 'The current page host or protected surface no longer matches the context that was approved for this intent.',
318
+ message: 'Protected fill was blocked because the page or protected surface no longer matches the approved request context.',
319
+ reason: 'The current page host or protected surface no longer matches the context that was approved for this request.',
222
320
  fillRef,
223
- intentId,
224
- ...serializeSecretIntentContext(completedIntent),
225
- intentStatus: completedIntent.status,
321
+ requestId,
322
+ ...serializeSecretRequestContext(latestRequest),
323
+ requestStatus: latestRequest.status,
226
324
  nextAction: 'reobserve',
227
- intentReusable: false,
325
+ requestReusable: true,
228
326
  mismatchReasons: contextMismatchReasons,
229
- ...(latestIntent.host ? { expectedHost: latestIntent.host } : {}),
327
+ ...(latestRequest.host ? { expectedHost: latestRequest.host } : {}),
230
328
  ...(observedHost ? { observedHost } : {}),
231
- ...(latestIntent.scopeRef ? { expectedScopeRef: latestIntent.scopeRef } : {}),
329
+ ...(latestRequest.scopeRef ? { expectedScopeRef: latestRequest.scopeRef } : {}),
232
330
  ...(fillableForm.scopeRef ? { observedScopeRef: fillableForm.scopeRef } : {}),
233
- },
331
+ }),
234
332
  };
235
333
  }
236
334
  else {
237
- const protectedValues = { ...cachedSecret.values };
238
- const storedSecretCandidate = fillableForm.storedSecretCandidates.find((candidate) => candidate.storedSecretRef === latestIntent.storedSecretRef);
239
- const executableForm = restrictFormToCandidateFields(fillableForm, storedSecretCandidate);
335
+ let claimedAt = new Date().toISOString();
336
+ let claimStoredSecretRef = latestRequest.storedSecretRef;
337
+ let claimKind = latestRequest.kind;
338
+ let protectedValues = null;
240
339
  try {
241
- const execution = await executeProtectedFill({
242
- session,
243
- page,
244
- fillableForm: executableForm,
245
- protectedValues,
246
- fieldPolicies: storedSecretCandidate?.fieldPolicies,
247
- });
248
- if (execution.kind === 'success') {
249
- const completedIntent = completeIntent(session, intentId);
250
- activateProtectedExposure(session, fillableForm, intentId, 'protected_fill_success');
340
+ const claim = await getSecretBackend().claimSecretRequest(session.intentSessionId, requestId, fillSecretStep?.stepId ?? `claim-${Date.now()}`);
341
+ claimedAt = claim.issuedAt;
342
+ claimStoredSecretRef = claim.secret.storedSecretRef ?? claimStoredSecretRef;
343
+ claimKind = claim.secret.kind ?? claimKind;
344
+ protectedValues = { ...claim.secret.values };
345
+ }
346
+ catch (error) {
347
+ const parsed = parseClaimFailure(error);
348
+ if (parsed === 'not_fulfilled') {
251
349
  finalResult = {
252
- success: true,
253
- payload: {
254
- fillRef,
255
- intentId,
256
- ...serializeSecretIntentContext(completedIntent),
257
- filledFields: execution.filledFields,
258
- intentStatus: completedIntent.status,
259
- message: 'Protected fill completed successfully.',
260
- reason: 'AgentBrowse filled the protected fields using the one-time secret payload.',
261
- },
350
+ success: false,
351
+ payload: failureForUnfulfilledRequest(fillableForm, latestRequest, fillRef, requestId),
262
352
  };
263
353
  }
264
- else if (execution.kind === 'binding_stale') {
265
- const completedIntent = completeIntent(session, intentId);
266
- activateProtectedExposure(session, fillableForm, intentId, 'protected_fill_binding_stale');
354
+ else if (parsed === 'already_claimed') {
267
355
  finalResult = {
268
356
  success: false,
269
- payload: {
270
- error: 'secret_fill_binding_stale',
271
- outcomeType: 'binding_stale',
272
- message: 'Protected fill was blocked because the stored field bindings are no longer valid on the page.',
357
+ payload: withFillableFormPresence(fillableForm, {
358
+ error: 'secret_request_already_claimed',
359
+ outcomeType: 'blocked',
360
+ message: 'Protected fill could not continue because this one-time secret request was already claimed earlier.',
361
+ reason: 'Secret values can only be claimed once for this request, so AgentBrowse needs a new request before it can fill again.',
273
362
  fillRef,
274
- intentId,
275
- ...serializeSecretIntentContext(completedIntent),
276
- intentStatus: completedIntent.status,
277
- nextAction: 'reobserve',
278
- targetRef: execution.targetRef,
279
- fieldKeys: execution.fieldKeys,
280
- reason: execution.reason,
281
- attempts: execution.attempts,
282
- },
363
+ requestId,
364
+ ...serializeSecretRequestContext(latestRequest),
365
+ requestStatus: latestRequest.status,
366
+ nextAction: 'request-secret',
367
+ }),
283
368
  };
284
369
  }
285
- else if (execution.kind === 'validation_failed') {
286
- const completedIntent = completeIntent(session, intentId);
287
- activateProtectedExposure(session, fillableForm, intentId, 'protected_fill_validation_failed');
370
+ else {
288
371
  finalResult = {
289
372
  success: false,
290
- payload: {
291
- error: 'secret_validation_failed',
373
+ payload: withFillableFormPresence(fillableForm, {
374
+ error: 'secret_claim_failed',
292
375
  outcomeType: 'blocked',
293
- message: 'Protected fill stopped because the stored data did not pass client-side validation.',
294
- reason: 'Protected execution reported client-side validation errors for one or more filled fields.',
376
+ message: 'Protected fill could not continue because AgentBrowse failed to claim the one-time secret payload.',
377
+ reason: error instanceof Error ? error.message : String(error),
295
378
  fillRef,
296
- intentId,
297
- ...serializeSecretIntentContext(completedIntent),
298
- intentStatus: completedIntent.status,
299
- executionResult: 'validation_failed',
300
- nextAction: 'ask-user',
301
- manualOverrideAllowed: true,
302
- filledFields: execution.filledFields,
303
- fieldErrors: execution.fieldErrors,
304
- },
379
+ requestId,
380
+ ...serializeSecretRequestContext(latestRequest),
381
+ requestStatus: latestRequest.status,
382
+ nextAction: error instanceof AgentpayRequestError && error.status === 404 ? 'poll-secret' : 'ask-user',
383
+ }),
305
384
  };
306
385
  }
307
- else {
308
- const completedIntent = completeIntent(session, intentId);
309
- activateProtectedExposure(session, fillableForm, intentId, 'protected_fill_unexpected_error');
386
+ }
387
+ if (!finalResult) {
388
+ if (!protectedValues) {
389
+ throw new Error('claimed_secret_values_missing');
390
+ }
391
+ const claimedRequest = markRequestClaimed(session, requestId, claimedAt);
392
+ if (claimStoredSecretRef || claimKind) {
393
+ persistRequestSnapshot(session, {
394
+ ...claimedRequest,
395
+ ...(claimStoredSecretRef ? { storedSecretRef: claimStoredSecretRef } : {}),
396
+ ...(claimKind ? { kind: claimKind } : {}),
397
+ });
398
+ }
399
+ const storedSecretCandidate = fillableForm.storedSecretCandidates.find((candidate) => candidate.storedSecretRef === (claimStoredSecretRef ?? latestRequest.storedSecretRef));
400
+ const executableForm = restrictFormToCandidateFields(fillableForm, storedSecretCandidate);
401
+ try {
402
+ const execution = await executeProtectedFill({
403
+ session,
404
+ page,
405
+ fillableForm: executableForm,
406
+ protectedValues,
407
+ fieldPolicies: storedSecretCandidate?.fieldPolicies,
408
+ });
409
+ if (execution.kind === 'success') {
410
+ activateProtectedExposure(session, fillableForm, requestId, 'protected_fill_success');
411
+ finalResult = {
412
+ success: true,
413
+ payload: withFillableFormPresence(fillableForm, {
414
+ fillRef,
415
+ requestId,
416
+ ...serializeSecretRequestContext(getSecretRequestSnapshot(session, requestId)),
417
+ filledFields: execution.filledFields,
418
+ requestStatus: latestRequest.status,
419
+ message: 'Protected fill completed successfully.',
420
+ reason: 'AgentBrowse filled the protected fields using the claimed one-time secret payload.',
421
+ }),
422
+ };
423
+ }
424
+ else if (execution.kind === 'binding_stale') {
425
+ activateProtectedExposure(session, fillableForm, requestId, 'protected_fill_binding_stale');
426
+ finalResult = {
427
+ success: false,
428
+ payload: withFillableFormPresence(fillableForm, {
429
+ error: 'secret_fill_binding_stale',
430
+ outcomeType: 'binding_stale',
431
+ message: 'Protected fill was blocked because the stored field bindings are no longer valid on the page.',
432
+ fillRef,
433
+ requestId,
434
+ ...serializeSecretRequestContext(getSecretRequestSnapshot(session, requestId)),
435
+ requestStatus: latestRequest.status,
436
+ nextAction: 'reobserve',
437
+ requestReusable: false,
438
+ targetRef: execution.targetRef,
439
+ fieldKeys: execution.fieldKeys,
440
+ reason: execution.reason,
441
+ attempts: execution.attempts,
442
+ }),
443
+ };
444
+ }
445
+ else if (execution.kind === 'validation_failed') {
446
+ activateProtectedExposure(session, fillableForm, requestId, 'protected_fill_validation_failed');
447
+ finalResult = {
448
+ success: false,
449
+ payload: withFillableFormPresence(fillableForm, {
450
+ error: 'secret_validation_failed',
451
+ outcomeType: 'blocked',
452
+ message: 'Protected fill stopped because the stored data did not pass client-side validation.',
453
+ reason: 'Protected execution reported client-side validation errors for one or more filled fields.',
454
+ fillRef,
455
+ requestId,
456
+ ...serializeSecretRequestContext(getSecretRequestSnapshot(session, requestId)),
457
+ requestStatus: latestRequest.status,
458
+ executionResult: 'validation_failed',
459
+ nextAction: 'ask-user',
460
+ manualOverrideAllowed: true,
461
+ requestReusable: false,
462
+ filledFields: execution.filledFields,
463
+ fieldErrors: execution.fieldErrors,
464
+ }),
465
+ };
466
+ }
467
+ else {
468
+ activateProtectedExposure(session, fillableForm, requestId, 'protected_fill_unexpected_error');
469
+ finalResult = {
470
+ success: false,
471
+ payload: withFillableFormPresence(fillableForm, {
472
+ error: 'protected_fill_execution_failed',
473
+ outcomeType: 'blocked',
474
+ message: 'Protected fill failed during protected execution, so this claimed request must not be reused.',
475
+ fillRef,
476
+ requestId,
477
+ ...serializeSecretRequestContext(getSecretRequestSnapshot(session, requestId)),
478
+ requestStatus: latestRequest.status,
479
+ requestReusable: false,
480
+ nextAction: 'ask-user',
481
+ reason: execution.reason,
482
+ }),
483
+ };
484
+ }
485
+ }
486
+ catch (error) {
487
+ activateProtectedExposure(session, fillableForm, requestId, 'protected_fill_unexpected_error');
310
488
  finalResult = {
311
489
  success: false,
312
- payload: {
490
+ payload: withFillableFormPresence(fillableForm, {
313
491
  error: 'protected_fill_execution_failed',
314
492
  outcomeType: 'blocked',
315
- message: 'Protected fill failed during protected execution, so this approved intent must not be reused.',
493
+ message: 'Protected fill failed during protected execution, so this claimed request must not be reused.',
316
494
  fillRef,
317
- intentId,
318
- ...serializeSecretIntentContext(completedIntent),
319
- intentStatus: completedIntent.status,
495
+ requestId,
496
+ ...serializeSecretRequestContext(getSecretRequestSnapshot(session, requestId)),
497
+ requestStatus: latestRequest.status,
498
+ requestReusable: false,
320
499
  nextAction: 'ask-user',
321
- reason: execution.reason,
322
- },
500
+ reason: error instanceof Error ? error.message : String(error),
501
+ }),
323
502
  };
324
503
  }
325
504
  }
326
- catch (error) {
327
- const completedIntent = completeIntent(session, intentId);
328
- activateProtectedExposure(session, fillableForm, intentId, 'protected_fill_unexpected_error');
329
- finalResult = {
330
- success: false,
331
- payload: {
332
- error: 'protected_fill_execution_failed',
333
- outcomeType: 'blocked',
334
- message: 'Protected fill failed during protected execution, so this approved intent must not be reused.',
335
- fillRef,
336
- intentId,
337
- ...serializeSecretIntentContext(completedIntent),
338
- intentStatus: completedIntent.status,
339
- nextAction: 'ask-user',
340
- reason: error instanceof Error ? error.message : String(error),
341
- },
342
- };
343
- }
344
505
  }
345
506
  }
507
+ catch (error) {
508
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, withFillableFormPresence(fillableForm, {
509
+ error: 'browser_connection_failed',
510
+ message: 'Protected fill could not connect to the browser.',
511
+ fillRef,
512
+ requestId,
513
+ ...serializeSecretRequestContext(latestRequest),
514
+ reason: error instanceof Error ? error.message : String(error),
515
+ }), {
516
+ pageRef: fillableForm.pageRef,
517
+ fillRef,
518
+ requestId,
519
+ });
520
+ }
346
521
  finally {
347
- deleteCachedTransientSecret(session, intentId);
348
- saveSession(session);
349
522
  if (browser) {
350
- disconnectPlaywright(browser);
523
+ await disconnectPlaywright(browser);
351
524
  }
352
525
  }
353
526
  if (!finalResult) {
354
- return outputFailure({
527
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, withFillableFormPresence(fillableForm, {
355
528
  error: 'protected_fill_execution_failed',
356
529
  outcomeType: 'blocked',
357
530
  message: 'Protected fill did not produce a final result.',
358
531
  reason: 'Protected execution finished without returning a terminal success or failure payload.',
359
532
  fillRef,
360
- intentId,
361
- ...serializeSecretIntentContext(latestIntent),
533
+ requestId,
534
+ ...serializeSecretRequestContext(latestRequest),
535
+ }), {
536
+ pageRef: fillableForm.pageRef,
537
+ fillRef,
538
+ requestId,
362
539
  });
363
540
  }
364
541
  if (!finalResult.success) {
365
- return outputFailure(finalResult.payload);
542
+ return emitFillSecretFailure(session, session.activeRunId, fillSecretStep?.stepId, finalResult.payload, {
543
+ pageRef: fillableForm.pageRef,
544
+ fillRef,
545
+ requestId,
546
+ });
366
547
  }
367
- return outputJSON({
548
+ return emitFillSecretSuccess(session, session.activeRunId, fillSecretStep?.stepId, {
368
549
  success: true,
369
550
  ...finalResult.payload,
551
+ }, {
552
+ pageRef: fillableForm.pageRef,
553
+ fillRef,
554
+ requestId,
370
555
  });
371
556
  }