@predicatelabs/sdk 0.99.9

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 (302) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +252 -0
  3. package/dist/actions.d.ts +185 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +1120 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/agent-runtime.d.ts +352 -0
  8. package/dist/agent-runtime.d.ts.map +1 -0
  9. package/dist/agent-runtime.js +1170 -0
  10. package/dist/agent-runtime.js.map +1 -0
  11. package/dist/agent.d.ts +164 -0
  12. package/dist/agent.d.ts.map +1 -0
  13. package/dist/agent.js +408 -0
  14. package/dist/agent.js.map +1 -0
  15. package/dist/asserts/expect.d.ts +159 -0
  16. package/dist/asserts/expect.d.ts.map +1 -0
  17. package/dist/asserts/expect.js +547 -0
  18. package/dist/asserts/expect.js.map +1 -0
  19. package/dist/asserts/index.d.ts +58 -0
  20. package/dist/asserts/index.d.ts.map +1 -0
  21. package/dist/asserts/index.js +70 -0
  22. package/dist/asserts/index.js.map +1 -0
  23. package/dist/asserts/query.d.ts +199 -0
  24. package/dist/asserts/query.d.ts.map +1 -0
  25. package/dist/asserts/query.js +288 -0
  26. package/dist/asserts/query.js.map +1 -0
  27. package/dist/backends/actions.d.ts +119 -0
  28. package/dist/backends/actions.d.ts.map +1 -0
  29. package/dist/backends/actions.js +291 -0
  30. package/dist/backends/actions.js.map +1 -0
  31. package/dist/backends/browser-use-adapter.d.ts +131 -0
  32. package/dist/backends/browser-use-adapter.d.ts.map +1 -0
  33. package/dist/backends/browser-use-adapter.js +219 -0
  34. package/dist/backends/browser-use-adapter.js.map +1 -0
  35. package/dist/backends/cdp-backend.d.ts +66 -0
  36. package/dist/backends/cdp-backend.d.ts.map +1 -0
  37. package/dist/backends/cdp-backend.js +273 -0
  38. package/dist/backends/cdp-backend.js.map +1 -0
  39. package/dist/backends/index.d.ts +80 -0
  40. package/dist/backends/index.d.ts.map +1 -0
  41. package/dist/backends/index.js +101 -0
  42. package/dist/backends/index.js.map +1 -0
  43. package/dist/backends/protocol.d.ts +156 -0
  44. package/dist/backends/protocol.d.ts.map +1 -0
  45. package/dist/backends/protocol.js +16 -0
  46. package/dist/backends/protocol.js.map +1 -0
  47. package/dist/backends/sentience-context.d.ts +143 -0
  48. package/dist/backends/sentience-context.d.ts.map +1 -0
  49. package/dist/backends/sentience-context.js +359 -0
  50. package/dist/backends/sentience-context.js.map +1 -0
  51. package/dist/backends/snapshot.d.ts +188 -0
  52. package/dist/backends/snapshot.d.ts.map +1 -0
  53. package/dist/backends/snapshot.js +360 -0
  54. package/dist/backends/snapshot.js.map +1 -0
  55. package/dist/browser.d.ts +154 -0
  56. package/dist/browser.d.ts.map +1 -0
  57. package/dist/browser.js +920 -0
  58. package/dist/browser.js.map +1 -0
  59. package/dist/canonicalization.d.ts +126 -0
  60. package/dist/canonicalization.d.ts.map +1 -0
  61. package/dist/canonicalization.js +161 -0
  62. package/dist/canonicalization.js.map +1 -0
  63. package/dist/captcha/strategies.d.ts +12 -0
  64. package/dist/captcha/strategies.d.ts.map +1 -0
  65. package/dist/captcha/strategies.js +43 -0
  66. package/dist/captcha/strategies.js.map +1 -0
  67. package/dist/captcha/types.d.ts +45 -0
  68. package/dist/captcha/types.d.ts.map +1 -0
  69. package/dist/captcha/types.js +12 -0
  70. package/dist/captcha/types.js.map +1 -0
  71. package/dist/cli.d.ts +5 -0
  72. package/dist/cli.d.ts.map +1 -0
  73. package/dist/cli.js +422 -0
  74. package/dist/cli.js.map +1 -0
  75. package/dist/conversational-agent.d.ts +123 -0
  76. package/dist/conversational-agent.d.ts.map +1 -0
  77. package/dist/conversational-agent.js +341 -0
  78. package/dist/conversational-agent.js.map +1 -0
  79. package/dist/cursor-policy.d.ts +41 -0
  80. package/dist/cursor-policy.d.ts.map +1 -0
  81. package/dist/cursor-policy.js +81 -0
  82. package/dist/cursor-policy.js.map +1 -0
  83. package/dist/debugger.d.ts +28 -0
  84. package/dist/debugger.d.ts.map +1 -0
  85. package/dist/debugger.js +107 -0
  86. package/dist/debugger.js.map +1 -0
  87. package/dist/expect.d.ts +16 -0
  88. package/dist/expect.d.ts.map +1 -0
  89. package/dist/expect.js +67 -0
  90. package/dist/expect.js.map +1 -0
  91. package/dist/failure-artifacts.d.ts +95 -0
  92. package/dist/failure-artifacts.d.ts.map +1 -0
  93. package/dist/failure-artifacts.js +805 -0
  94. package/dist/failure-artifacts.js.map +1 -0
  95. package/dist/generator.d.ts +16 -0
  96. package/dist/generator.d.ts.map +1 -0
  97. package/dist/generator.js +205 -0
  98. package/dist/generator.js.map +1 -0
  99. package/dist/index.d.ts +37 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +160 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/inspector.d.ts +13 -0
  104. package/dist/inspector.d.ts.map +1 -0
  105. package/dist/inspector.js +153 -0
  106. package/dist/inspector.js.map +1 -0
  107. package/dist/llm-provider.d.ts +144 -0
  108. package/dist/llm-provider.d.ts.map +1 -0
  109. package/dist/llm-provider.js +460 -0
  110. package/dist/llm-provider.js.map +1 -0
  111. package/dist/ordinal.d.ts +90 -0
  112. package/dist/ordinal.d.ts.map +1 -0
  113. package/dist/ordinal.js +249 -0
  114. package/dist/ordinal.js.map +1 -0
  115. package/dist/overlay.d.ts +63 -0
  116. package/dist/overlay.d.ts.map +1 -0
  117. package/dist/overlay.js +102 -0
  118. package/dist/overlay.js.map +1 -0
  119. package/dist/protocols/browser-protocol.d.ts +79 -0
  120. package/dist/protocols/browser-protocol.d.ts.map +1 -0
  121. package/dist/protocols/browser-protocol.js +9 -0
  122. package/dist/protocols/browser-protocol.js.map +1 -0
  123. package/dist/query.d.ts +66 -0
  124. package/dist/query.d.ts.map +1 -0
  125. package/dist/query.js +482 -0
  126. package/dist/query.js.map +1 -0
  127. package/dist/read.d.ts +47 -0
  128. package/dist/read.d.ts.map +1 -0
  129. package/dist/read.js +128 -0
  130. package/dist/read.js.map +1 -0
  131. package/dist/recorder.d.ts +44 -0
  132. package/dist/recorder.d.ts.map +1 -0
  133. package/dist/recorder.js +262 -0
  134. package/dist/recorder.js.map +1 -0
  135. package/dist/runtime-agent.d.ts +72 -0
  136. package/dist/runtime-agent.d.ts.map +1 -0
  137. package/dist/runtime-agent.js +357 -0
  138. package/dist/runtime-agent.js.map +1 -0
  139. package/dist/screenshot.d.ts +17 -0
  140. package/dist/screenshot.d.ts.map +1 -0
  141. package/dist/screenshot.js +40 -0
  142. package/dist/screenshot.js.map +1 -0
  143. package/dist/snapshot-diff.d.ts +23 -0
  144. package/dist/snapshot-diff.d.ts.map +1 -0
  145. package/dist/snapshot-diff.js +119 -0
  146. package/dist/snapshot-diff.js.map +1 -0
  147. package/dist/snapshot.d.ts +47 -0
  148. package/dist/snapshot.d.ts.map +1 -0
  149. package/dist/snapshot.js +358 -0
  150. package/dist/snapshot.js.map +1 -0
  151. package/dist/textSearch.d.ts +64 -0
  152. package/dist/textSearch.d.ts.map +1 -0
  153. package/dist/textSearch.js +113 -0
  154. package/dist/textSearch.js.map +1 -0
  155. package/dist/tools/context.d.ts +18 -0
  156. package/dist/tools/context.d.ts.map +1 -0
  157. package/dist/tools/context.js +40 -0
  158. package/dist/tools/context.js.map +1 -0
  159. package/dist/tools/defaults.d.ts +5 -0
  160. package/dist/tools/defaults.d.ts.map +1 -0
  161. package/dist/tools/defaults.js +368 -0
  162. package/dist/tools/defaults.js.map +1 -0
  163. package/dist/tools/filesystem.d.ts +12 -0
  164. package/dist/tools/filesystem.d.ts.map +1 -0
  165. package/dist/tools/filesystem.js +137 -0
  166. package/dist/tools/filesystem.js.map +1 -0
  167. package/dist/tools/index.d.ts +5 -0
  168. package/dist/tools/index.d.ts.map +1 -0
  169. package/dist/tools/index.js +15 -0
  170. package/dist/tools/index.js.map +1 -0
  171. package/dist/tools/registry.d.ts +38 -0
  172. package/dist/tools/registry.d.ts.map +1 -0
  173. package/dist/tools/registry.js +100 -0
  174. package/dist/tools/registry.js.map +1 -0
  175. package/dist/tracing/cloud-sink.d.ts +189 -0
  176. package/dist/tracing/cloud-sink.d.ts.map +1 -0
  177. package/dist/tracing/cloud-sink.js +1067 -0
  178. package/dist/tracing/cloud-sink.js.map +1 -0
  179. package/dist/tracing/index-schema.d.ts +231 -0
  180. package/dist/tracing/index-schema.d.ts.map +1 -0
  181. package/dist/tracing/index-schema.js +235 -0
  182. package/dist/tracing/index-schema.js.map +1 -0
  183. package/dist/tracing/index.d.ts +12 -0
  184. package/dist/tracing/index.d.ts.map +1 -0
  185. package/dist/tracing/index.js +28 -0
  186. package/dist/tracing/index.js.map +1 -0
  187. package/dist/tracing/indexer.d.ts +20 -0
  188. package/dist/tracing/indexer.d.ts.map +1 -0
  189. package/dist/tracing/indexer.js +347 -0
  190. package/dist/tracing/indexer.js.map +1 -0
  191. package/dist/tracing/jsonl-sink.d.ts +51 -0
  192. package/dist/tracing/jsonl-sink.d.ts.map +1 -0
  193. package/dist/tracing/jsonl-sink.js +329 -0
  194. package/dist/tracing/jsonl-sink.js.map +1 -0
  195. package/dist/tracing/sink.d.ts +25 -0
  196. package/dist/tracing/sink.d.ts.map +1 -0
  197. package/dist/tracing/sink.js +15 -0
  198. package/dist/tracing/sink.js.map +1 -0
  199. package/dist/tracing/tracer-factory.d.ts +102 -0
  200. package/dist/tracing/tracer-factory.d.ts.map +1 -0
  201. package/dist/tracing/tracer-factory.js +375 -0
  202. package/dist/tracing/tracer-factory.js.map +1 -0
  203. package/dist/tracing/tracer.d.ts +140 -0
  204. package/dist/tracing/tracer.d.ts.map +1 -0
  205. package/dist/tracing/tracer.js +336 -0
  206. package/dist/tracing/tracer.js.map +1 -0
  207. package/dist/tracing/types.d.ts +203 -0
  208. package/dist/tracing/types.d.ts.map +1 -0
  209. package/dist/tracing/types.js +8 -0
  210. package/dist/tracing/types.js.map +1 -0
  211. package/dist/types.d.ts +422 -0
  212. package/dist/types.d.ts.map +1 -0
  213. package/dist/types.js +6 -0
  214. package/dist/types.js.map +1 -0
  215. package/dist/utils/action-executor.d.ts +25 -0
  216. package/dist/utils/action-executor.d.ts.map +1 -0
  217. package/dist/utils/action-executor.js +121 -0
  218. package/dist/utils/action-executor.js.map +1 -0
  219. package/dist/utils/browser-evaluator.d.ts +76 -0
  220. package/dist/utils/browser-evaluator.d.ts.map +1 -0
  221. package/dist/utils/browser-evaluator.js +130 -0
  222. package/dist/utils/browser-evaluator.js.map +1 -0
  223. package/dist/utils/browser.d.ts +30 -0
  224. package/dist/utils/browser.d.ts.map +1 -0
  225. package/dist/utils/browser.js +75 -0
  226. package/dist/utils/browser.js.map +1 -0
  227. package/dist/utils/element-filter.d.ts +76 -0
  228. package/dist/utils/element-filter.d.ts.map +1 -0
  229. package/dist/utils/element-filter.js +195 -0
  230. package/dist/utils/element-filter.js.map +1 -0
  231. package/dist/utils/grid-utils.d.ts +37 -0
  232. package/dist/utils/grid-utils.d.ts.map +1 -0
  233. package/dist/utils/grid-utils.js +283 -0
  234. package/dist/utils/grid-utils.js.map +1 -0
  235. package/dist/utils/llm-interaction-handler.d.ts +41 -0
  236. package/dist/utils/llm-interaction-handler.d.ts.map +1 -0
  237. package/dist/utils/llm-interaction-handler.js +171 -0
  238. package/dist/utils/llm-interaction-handler.js.map +1 -0
  239. package/dist/utils/llm-response-builder.d.ts +56 -0
  240. package/dist/utils/llm-response-builder.d.ts.map +1 -0
  241. package/dist/utils/llm-response-builder.js +130 -0
  242. package/dist/utils/llm-response-builder.js.map +1 -0
  243. package/dist/utils/selector-utils.d.ts +12 -0
  244. package/dist/utils/selector-utils.d.ts.map +1 -0
  245. package/dist/utils/selector-utils.js +32 -0
  246. package/dist/utils/selector-utils.js.map +1 -0
  247. package/dist/utils/snapshot-event-builder.d.ts +28 -0
  248. package/dist/utils/snapshot-event-builder.d.ts.map +1 -0
  249. package/dist/utils/snapshot-event-builder.js +88 -0
  250. package/dist/utils/snapshot-event-builder.js.map +1 -0
  251. package/dist/utils/snapshot-processor.d.ts +27 -0
  252. package/dist/utils/snapshot-processor.d.ts.map +1 -0
  253. package/dist/utils/snapshot-processor.js +47 -0
  254. package/dist/utils/snapshot-processor.js.map +1 -0
  255. package/dist/utils/trace-event-builder.d.ts +122 -0
  256. package/dist/utils/trace-event-builder.d.ts.map +1 -0
  257. package/dist/utils/trace-event-builder.js +365 -0
  258. package/dist/utils/trace-event-builder.js.map +1 -0
  259. package/dist/utils/trace-file-manager.d.ts +70 -0
  260. package/dist/utils/trace-file-manager.d.ts.map +1 -0
  261. package/dist/utils/trace-file-manager.js +194 -0
  262. package/dist/utils/trace-file-manager.js.map +1 -0
  263. package/dist/utils/zod.d.ts +5 -0
  264. package/dist/utils/zod.d.ts.map +1 -0
  265. package/dist/utils/zod.js +80 -0
  266. package/dist/utils/zod.js.map +1 -0
  267. package/dist/utils.d.ts +8 -0
  268. package/dist/utils.d.ts.map +1 -0
  269. package/dist/utils.js +13 -0
  270. package/dist/utils.js.map +1 -0
  271. package/dist/verification.d.ts +194 -0
  272. package/dist/verification.d.ts.map +1 -0
  273. package/dist/verification.js +530 -0
  274. package/dist/verification.js.map +1 -0
  275. package/dist/vision-executor.d.ts +18 -0
  276. package/dist/vision-executor.d.ts.map +1 -0
  277. package/dist/vision-executor.js +60 -0
  278. package/dist/vision-executor.js.map +1 -0
  279. package/dist/visual-agent.d.ts +120 -0
  280. package/dist/visual-agent.d.ts.map +1 -0
  281. package/dist/visual-agent.js +796 -0
  282. package/dist/visual-agent.js.map +1 -0
  283. package/dist/wait.d.ts +35 -0
  284. package/dist/wait.d.ts.map +1 -0
  285. package/dist/wait.js +76 -0
  286. package/dist/wait.js.map +1 -0
  287. package/package.json +94 -0
  288. package/spec/README.md +72 -0
  289. package/spec/SNAPSHOT_V1.md +208 -0
  290. package/spec/sdk-types.md +259 -0
  291. package/spec/snapshot.schema.json +148 -0
  292. package/src/extension/background.js +104 -0
  293. package/src/extension/content.js +162 -0
  294. package/src/extension/injected_api.js +1399 -0
  295. package/src/extension/manifest.json +36 -0
  296. package/src/extension/pkg/README.md +1340 -0
  297. package/src/extension/pkg/package.json +15 -0
  298. package/src/extension/pkg/sentience_core.d.ts +51 -0
  299. package/src/extension/pkg/sentience_core.js +371 -0
  300. package/src/extension/pkg/sentience_core_bg.wasm +0 -0
  301. package/src/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
  302. package/src/extension/release.json +116 -0
@@ -0,0 +1,1170 @@
1
+ "use strict";
2
+ /**
3
+ * Agent runtime for verification loop support.
4
+ *
5
+ * This module provides a thin runtime wrapper that combines:
6
+ * 1. Browser session management
7
+ * 2. Snapshot/query helpers
8
+ * 3. Tracer for event emission
9
+ * 4. Assertion/verification methods
10
+ *
11
+ * The AgentRuntime is designed to be used in agent verification loops where
12
+ * you need to repeatedly take snapshots, execute actions, and verify results.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { SentienceBrowser } from './browser';
17
+ * import { AgentRuntime } from './agent-runtime';
18
+ * import { urlMatches, exists } from './verification';
19
+ * import { Tracer, JsonlTraceSink } from './tracing';
20
+ *
21
+ * const browser = await SentienceBrowser.create();
22
+ * const page = await browser.newPage();
23
+ * await page.goto("https://example.com");
24
+ *
25
+ * const sink = new JsonlTraceSink("trace.jsonl");
26
+ * const tracer = new Tracer("test-run", sink);
27
+ *
28
+ * const runtime = new AgentRuntime(browser, page, tracer);
29
+ *
30
+ * // Take snapshot and run assertions
31
+ * await runtime.snapshot();
32
+ * runtime.assert(urlMatches(/example\.com/), "on_homepage");
33
+ * runtime.assert(exists("role=button"), "has_buttons");
34
+ *
35
+ * // Check if task is done
36
+ * if (runtime.assertDone(exists("text~'Success'"), "task_complete")) {
37
+ * console.log("Task completed!");
38
+ * }
39
+ * ```
40
+ */
41
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
42
+ if (k2 === undefined) k2 = k;
43
+ var desc = Object.getOwnPropertyDescriptor(m, k);
44
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
45
+ desc = { enumerable: true, get: function() { return m[k]; } };
46
+ }
47
+ Object.defineProperty(o, k2, desc);
48
+ }) : (function(o, m, k, k2) {
49
+ if (k2 === undefined) k2 = k;
50
+ o[k2] = m[k];
51
+ }));
52
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
53
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
54
+ }) : function(o, v) {
55
+ o["default"] = v;
56
+ });
57
+ var __importStar = (this && this.__importStar) || (function () {
58
+ var ownKeys = function(o) {
59
+ ownKeys = Object.getOwnPropertyNames || function (o) {
60
+ var ar = [];
61
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
62
+ return ar;
63
+ };
64
+ return ownKeys(o);
65
+ };
66
+ return function (mod) {
67
+ if (mod && mod.__esModule) return mod;
68
+ var result = {};
69
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
70
+ __setModuleDefault(result, mod);
71
+ return result;
72
+ };
73
+ })();
74
+ Object.defineProperty(exports, "__esModule", { value: true });
75
+ exports.AgentRuntime = exports.AssertionHandle = void 0;
76
+ const fs = __importStar(require("fs"));
77
+ const path = __importStar(require("path"));
78
+ const trace_event_builder_1 = require("./utils/trace-event-builder");
79
+ const failure_artifacts_1 = require("./failure-artifacts");
80
+ const browser_1 = require("./browser");
81
+ const types_1 = require("./captcha/types");
82
+ const DEFAULT_CAPTCHA_OPTIONS = {
83
+ policy: 'abort',
84
+ minConfidence: 0.7,
85
+ timeoutMs: 120000,
86
+ pollMs: 1000,
87
+ maxRetriesNewSession: 1,
88
+ };
89
+ class AssertionHandle {
90
+ constructor(runtime, predicate, label, required) {
91
+ this.runtime = runtime;
92
+ this.predicate = predicate;
93
+ this.label = label;
94
+ this.required = required;
95
+ }
96
+ once() {
97
+ return this.runtime.assert(this.predicate, this.label, this.required);
98
+ }
99
+ async eventually(options = {}) {
100
+ const timeoutMs = options.timeoutMs ?? 10000;
101
+ const pollMs = options.pollMs ?? 250;
102
+ const snapshotOptions = options.snapshotOptions;
103
+ const minConfidence = options.minConfidence;
104
+ const maxSnapshotAttempts = options.maxSnapshotAttempts ?? 3;
105
+ const visionProvider = options.visionProvider;
106
+ const visionSystemPrompt = options.visionSystemPrompt;
107
+ const visionUserPrompt = options.visionUserPrompt;
108
+ const deadline = Date.now() + timeoutMs;
109
+ let attempt = 0;
110
+ let snapshotAttempt = 0;
111
+ let lastOutcome = null;
112
+ while (true) {
113
+ attempt += 1;
114
+ await this.runtime.snapshot(snapshotOptions);
115
+ snapshotAttempt += 1;
116
+ const diagnostics = this.runtime.lastSnapshot?.diagnostics;
117
+ const confidence = diagnostics?.confidence;
118
+ if (typeof minConfidence === 'number' &&
119
+ typeof confidence === 'number' &&
120
+ Number.isFinite(confidence) &&
121
+ confidence < minConfidence) {
122
+ lastOutcome = {
123
+ passed: false,
124
+ reason: `Snapshot confidence ${confidence.toFixed(3)} < minConfidence ${minConfidence.toFixed(3)}`,
125
+ details: {
126
+ reason_code: 'snapshot_low_confidence',
127
+ confidence,
128
+ min_confidence: minConfidence,
129
+ snapshot_attempt: snapshotAttempt,
130
+ diagnostics,
131
+ },
132
+ };
133
+ this.runtime._recordOutcome(lastOutcome, this.label, this.required, { eventually: true, attempt, snapshot_attempt: snapshotAttempt, final: false }, false);
134
+ if (snapshotAttempt >= maxSnapshotAttempts) {
135
+ // Optional: vision fallback after snapshot exhaustion (last resort).
136
+ // Keeps the assertion surface invariant; only perception changes.
137
+ if (visionProvider && visionProvider.supportsVision?.()) {
138
+ try {
139
+ const buf = (await this.runtime.page.screenshot({ type: 'png' }));
140
+ const imageBase64 = Buffer.from(buf).toString('base64');
141
+ const sys = visionSystemPrompt ?? 'You are a strict visual verifier. Answer only YES or NO.';
142
+ const user = visionUserPrompt ??
143
+ `Given the screenshot, is the following condition satisfied?\n\n${this.label}\n\nAnswer YES or NO.`;
144
+ const resp = await visionProvider.generateWithImage(sys, user, imageBase64, {
145
+ temperature: 0.0,
146
+ });
147
+ const text = (resp.content || '').trim().toLowerCase();
148
+ const passed = text.startsWith('yes');
149
+ const finalOutcome = {
150
+ passed,
151
+ reason: passed ? 'vision_fallback_yes' : 'vision_fallback_no',
152
+ details: {
153
+ reason_code: passed ? 'vision_fallback_pass' : 'vision_fallback_fail',
154
+ vision_response: resp.content,
155
+ min_confidence: minConfidence,
156
+ snapshot_attempts: snapshotAttempt,
157
+ },
158
+ };
159
+ this.runtime._recordOutcome(finalOutcome, this.label, this.required, {
160
+ eventually: true,
161
+ attempt,
162
+ snapshot_attempt: snapshotAttempt,
163
+ final: true,
164
+ vision_fallback: true,
165
+ }, true);
166
+ if (this.required && !passed) {
167
+ this.runtime.persistFailureArtifacts(`assert_eventually_failed:${this.label}`);
168
+ }
169
+ return passed;
170
+ }
171
+ catch {
172
+ // fall through to snapshot_exhausted
173
+ }
174
+ }
175
+ const finalOutcome = {
176
+ passed: false,
177
+ reason: `Snapshot exhausted after ${snapshotAttempt} attempt(s) below minConfidence ${minConfidence.toFixed(3)}`,
178
+ details: {
179
+ reason_code: 'snapshot_exhausted',
180
+ confidence,
181
+ min_confidence: minConfidence,
182
+ snapshot_attempts: snapshotAttempt,
183
+ diagnostics,
184
+ },
185
+ };
186
+ this.runtime._recordOutcome(finalOutcome, this.label, this.required, {
187
+ eventually: true,
188
+ attempt,
189
+ snapshot_attempt: snapshotAttempt,
190
+ final: true,
191
+ exhausted: true,
192
+ }, true);
193
+ if (this.required) {
194
+ this.runtime.persistFailureArtifacts(`assert_eventually_failed:${this.label}`);
195
+ }
196
+ return false;
197
+ }
198
+ if (Date.now() >= deadline) {
199
+ this.runtime._recordOutcome(lastOutcome, this.label, this.required, {
200
+ eventually: true,
201
+ attempt,
202
+ snapshot_attempt: snapshotAttempt,
203
+ final: true,
204
+ timeout: true,
205
+ }, true);
206
+ if (this.required) {
207
+ this.runtime.persistFailureArtifacts(`assert_eventually_timeout:${this.label}`);
208
+ }
209
+ return false;
210
+ }
211
+ await new Promise(resolve => setTimeout(resolve, pollMs));
212
+ continue;
213
+ }
214
+ lastOutcome = this.predicate(this.runtime.ctx());
215
+ // Emit attempt event (not recorded in step_end)
216
+ this.runtime._recordOutcome(lastOutcome, this.label, this.required, { eventually: true, attempt, final: false }, false);
217
+ if (lastOutcome.passed) {
218
+ // Record final success once
219
+ this.runtime._recordOutcome(lastOutcome, this.label, this.required, { eventually: true, attempt, final: true }, true);
220
+ return true;
221
+ }
222
+ if (Date.now() >= deadline) {
223
+ // Record final failure once
224
+ this.runtime._recordOutcome(lastOutcome, this.label, this.required, { eventually: true, attempt, final: true, timeout: true }, true);
225
+ if (this.required) {
226
+ this.runtime.persistFailureArtifacts(`assert_eventually_timeout:${this.label}`);
227
+ }
228
+ return false;
229
+ }
230
+ await new Promise(resolve => setTimeout(resolve, pollMs));
231
+ }
232
+ }
233
+ }
234
+ exports.AssertionHandle = AssertionHandle;
235
+ /**
236
+ * Runtime wrapper for agent verification loops.
237
+ *
238
+ * Provides ergonomic methods for:
239
+ * - snapshot(): Take page snapshot
240
+ * - assert(): Evaluate assertion predicates
241
+ * - assertDone(): Assert task completion (required assertion)
242
+ *
243
+ * The runtime manages assertion state per step and emits verification events
244
+ * to the tracer for Studio timeline display.
245
+ */
246
+ class AgentRuntime {
247
+ static similarity(a, b) {
248
+ const s1 = a.toLowerCase();
249
+ const s2 = b.toLowerCase();
250
+ if (!s1 || !s2)
251
+ return 0;
252
+ if (s1 === s2)
253
+ return 1;
254
+ // Bigram overlap (cheap, robust enough for suggestions)
255
+ const bigrams = (s) => {
256
+ const out = [];
257
+ for (let i = 0; i < s.length - 1; i++)
258
+ out.push(s.slice(i, i + 2));
259
+ return out;
260
+ };
261
+ const a2 = bigrams(s1);
262
+ const b2 = bigrams(s2);
263
+ const setB = new Set(b2);
264
+ let common = 0;
265
+ for (const g of a2)
266
+ if (setB.has(g))
267
+ common += 1;
268
+ return (2 * common) / (a2.length + b2.length + 1e-9);
269
+ }
270
+ static stringifyEvalValue(value) {
271
+ if (value === null || value === undefined) {
272
+ return 'null';
273
+ }
274
+ if (Array.isArray(value) || typeof value === 'object') {
275
+ try {
276
+ return JSON.stringify(value);
277
+ }
278
+ catch {
279
+ return String(value);
280
+ }
281
+ }
282
+ return String(value);
283
+ }
284
+ _recordOutcome(outcome, label, required, extra, recordInStep) {
285
+ const details = { ...(outcome.details || {}) };
286
+ // Failure intelligence: nearest matches for selector-driven assertions
287
+ if (!outcome.passed && this.lastSnapshot && typeof details.selector === 'string') {
288
+ const selector = details.selector;
289
+ const scored = [];
290
+ for (const el of this.lastSnapshot.elements) {
291
+ const hay = el.name ?? el.text ?? '';
292
+ if (!hay)
293
+ continue;
294
+ const score = AgentRuntime.similarity(selector, hay);
295
+ scored.push({ score, el });
296
+ }
297
+ scored.sort((x, y) => y.score - x.score);
298
+ details.nearest_matches = scored.slice(0, 3).map(({ score, el }) => ({
299
+ id: el.id,
300
+ role: el.role,
301
+ text: (el.text ?? '').toString().slice(0, 80),
302
+ name: (el.name ?? '').toString().slice(0, 80),
303
+ score: Math.round(score * 10000) / 10000,
304
+ }));
305
+ }
306
+ const record = {
307
+ label,
308
+ passed: outcome.passed,
309
+ required,
310
+ reason: outcome.reason,
311
+ details,
312
+ ...(extra || {}),
313
+ };
314
+ if (recordInStep) {
315
+ this.assertionsThisStep.push(record);
316
+ }
317
+ this.tracer.emit('verification', {
318
+ kind: 'assert',
319
+ ...record,
320
+ }, this.stepId || undefined);
321
+ }
322
+ check(predicate, label, required = false) {
323
+ return new AssertionHandle(this, predicate, label, required);
324
+ }
325
+ /**
326
+ * Create AgentRuntime from a raw Playwright Page (sidecar mode).
327
+ */
328
+ static fromPlaywrightPage(page, tracer, options) {
329
+ const browser = options?.browser ??
330
+ (() => {
331
+ const sentienceBrowser = browser_1.SentienceBrowser.fromPage(page, options?.apiKey, options?.apiUrl);
332
+ return {
333
+ snapshot: async (_page, snapshotOptions) => sentienceBrowser.snapshot(snapshotOptions),
334
+ };
335
+ })();
336
+ return new AgentRuntime(browser, page, tracer, options?.toolRegistry);
337
+ }
338
+ /**
339
+ * Sidecar alias for fromPlaywrightPage().
340
+ */
341
+ static attach(page, tracer, options) {
342
+ return AgentRuntime.fromPlaywrightPage(page, tracer, options);
343
+ }
344
+ /**
345
+ * Create a new AgentRuntime.
346
+ *
347
+ * @param browser - Browser instance for taking snapshots
348
+ * @param page - Playwright Page for browser interaction
349
+ * @param tracer - Tracer for emitting verification events
350
+ */
351
+ constructor(browser, page, tracer, toolRegistry) {
352
+ /** Current step identifier */
353
+ this.stepId = null;
354
+ /** Current step index (0-based) */
355
+ // 0-based step indexing (first auto-generated stepId is "step-0")
356
+ this.stepIndex = -1;
357
+ /** Most recent snapshot (for assertion context) */
358
+ this.lastSnapshot = null;
359
+ this.stepPreSnapshot = null;
360
+ this.stepPreUrl = null;
361
+ /** Best-effort download records (Playwright downloads) */
362
+ this.downloads = [];
363
+ /** Tab registry for tab operations */
364
+ this.tabRegistry = new Map();
365
+ this.tabIds = new WeakMap();
366
+ /** Failure artifact buffer (Phase 1) */
367
+ this.artifactBuffer = null;
368
+ this.artifactTimer = null;
369
+ /** Assertions accumulated during current step */
370
+ this.assertionsThisStep = [];
371
+ this.stepGoal = null;
372
+ this.lastAction = null;
373
+ /** Task completion tracking */
374
+ this.taskDone = false;
375
+ this.taskDoneLabel = null;
376
+ /** CAPTCHA handling (optional, disabled by default) */
377
+ this.captchaOptions = null;
378
+ this.captchaRetryCount = 0;
379
+ this.browser = browser;
380
+ this.page = page;
381
+ this.tracer = tracer;
382
+ this.toolRegistry = toolRegistry;
383
+ // Best-effort download tracking (does not change behavior unless a download occurs).
384
+ try {
385
+ this.page.on('download', download => {
386
+ void this.trackDownload(download);
387
+ });
388
+ }
389
+ catch {
390
+ // ignore
391
+ }
392
+ }
393
+ capabilities() {
394
+ const hasTabs = typeof this.listTabs === 'function';
395
+ const hasEval = typeof this.evaluateJs === 'function';
396
+ const hasKeyboard = Boolean(this.page?.keyboard);
397
+ const hasDownloads = this.downloads.length >= 0;
398
+ let hasPermissions = false;
399
+ try {
400
+ const context = typeof this.page?.context === 'function' ? this.page.context() : null;
401
+ hasPermissions =
402
+ Boolean(context) &&
403
+ typeof context.clearPermissions === 'function' &&
404
+ typeof context.grantPermissions === 'function';
405
+ }
406
+ catch {
407
+ hasPermissions = false;
408
+ }
409
+ let hasFiles = false;
410
+ if (this.toolRegistry) {
411
+ hasFiles = Boolean(this.toolRegistry.get('read_file'));
412
+ }
413
+ return {
414
+ tabs: hasTabs,
415
+ evaluate_js: hasEval,
416
+ downloads: hasDownloads,
417
+ filesystem_tools: hasFiles,
418
+ keyboard: hasKeyboard,
419
+ permissions: hasPermissions,
420
+ };
421
+ }
422
+ can(name) {
423
+ return Boolean(this.capabilities()[name]);
424
+ }
425
+ /**
426
+ * Configure CAPTCHA handling (disabled by default unless set).
427
+ */
428
+ setCaptchaOptions(options) {
429
+ this.captchaOptions = {
430
+ ...DEFAULT_CAPTCHA_OPTIONS,
431
+ ...options,
432
+ };
433
+ this.captchaRetryCount = 0;
434
+ }
435
+ /**
436
+ * Build assertion context from current state.
437
+ */
438
+ ctx() {
439
+ let url = null;
440
+ if (this.lastSnapshot) {
441
+ url = this.lastSnapshot.url;
442
+ }
443
+ else if (this.page) {
444
+ url = this.page.url();
445
+ }
446
+ return {
447
+ snapshot: this.lastSnapshot,
448
+ url,
449
+ stepId: this.stepId,
450
+ downloads: this.downloads,
451
+ };
452
+ }
453
+ async trackDownload(download) {
454
+ const rec = {
455
+ status: 'started',
456
+ suggested_filename: download?.suggestedFilename?.() ?? download?.suggested_filename,
457
+ url: download?.url?.() ?? download?.url,
458
+ };
459
+ this.downloads.push(rec);
460
+ try {
461
+ const p = (await download.path?.());
462
+ rec.status = 'completed';
463
+ if (p) {
464
+ rec.path = p;
465
+ try {
466
+ // Best-effort size and mime type (no new deps).
467
+ rec.size_bytes = Number(fs.statSync(p).size);
468
+ const ext = String(path.extname(p) || '').toLowerCase();
469
+ const mimeByExt = {
470
+ '.pdf': 'application/pdf',
471
+ '.txt': 'text/plain',
472
+ '.csv': 'text/csv',
473
+ '.json': 'application/json',
474
+ '.zip': 'application/zip',
475
+ '.png': 'image/png',
476
+ '.jpg': 'image/jpeg',
477
+ '.jpeg': 'image/jpeg',
478
+ '.webp': 'image/webp',
479
+ };
480
+ if (mimeByExt[ext])
481
+ rec.mime_type = mimeByExt[ext];
482
+ }
483
+ catch {
484
+ // ignore
485
+ }
486
+ }
487
+ }
488
+ catch (e) {
489
+ rec.status = 'failed';
490
+ rec.error = String(e?.message ?? e);
491
+ }
492
+ }
493
+ /**
494
+ * Take a snapshot of the current page state.
495
+ *
496
+ * This updates lastSnapshot which is used as context for assertions.
497
+ * When emitTrace=true (default), automatically emits a 'snapshot' trace event
498
+ * with screenshot_base64 for Sentience Studio visualization.
499
+ *
500
+ * @param options - Options passed through to browser.snapshot()
501
+ * @param options.emitTrace - If true (default), emit a 'snapshot' trace event with screenshot.
502
+ * Set to false to disable automatic trace emission.
503
+ * @returns Snapshot of current page state
504
+ *
505
+ * @example
506
+ * // Default: snapshot with auto-emit trace event
507
+ * const snapshot = await runtime.snapshot();
508
+ *
509
+ * // Disable auto-emit for manual control
510
+ * const snapshot = await runtime.snapshot({ emitTrace: false });
511
+ * // Later, manually emit if needed:
512
+ * tracer.emitSnapshot(snapshot, runtime.getStepId());
513
+ */
514
+ async snapshot(options) {
515
+ const { _skipCaptchaHandling, emitTrace = true, ...snapshotOptions } = options || {};
516
+ this.lastSnapshot = await this.browser.snapshot(this.page, snapshotOptions);
517
+ if (this.lastSnapshot && !this.stepPreSnapshot) {
518
+ this.stepPreSnapshot = this.lastSnapshot;
519
+ this.stepPreUrl = this.lastSnapshot.url;
520
+ }
521
+ if (!_skipCaptchaHandling) {
522
+ await this.handleCaptchaIfNeeded(this.lastSnapshot, 'gateway');
523
+ }
524
+ // Auto-emit snapshot trace event for Studio visualization
525
+ if (emitTrace && this.lastSnapshot && this.tracer) {
526
+ this.emitSnapshotTrace(this.lastSnapshot);
527
+ }
528
+ return this.lastSnapshot;
529
+ }
530
+ /**
531
+ * Emit a snapshot trace event with screenshot for Studio visualization.
532
+ *
533
+ * This is called automatically by snapshot() when emitTrace=true.
534
+ */
535
+ emitSnapshotTrace(snapshot) {
536
+ if (!this.tracer) {
537
+ return;
538
+ }
539
+ try {
540
+ this.tracer.emitSnapshot(snapshot, this.stepId ?? undefined, this.stepIndex, 'jpeg');
541
+ }
542
+ catch {
543
+ // Best-effort: don't let trace emission errors break snapshot
544
+ }
545
+ }
546
+ /**
547
+ * Evaluate JavaScript in the page context.
548
+ */
549
+ async evaluateJs(request) {
550
+ try {
551
+ const value = await this.page.evaluate(request.code);
552
+ const text = AgentRuntime.stringifyEvalValue(value);
553
+ const maxChars = request.max_output_chars ?? 4000;
554
+ const truncate = request.truncate ?? true;
555
+ let truncated = false;
556
+ let finalText = text;
557
+ if (truncate && finalText.length > maxChars) {
558
+ finalText = `${finalText.slice(0, maxChars)}...`;
559
+ truncated = true;
560
+ }
561
+ return {
562
+ ok: true,
563
+ value,
564
+ text: finalText,
565
+ truncated,
566
+ };
567
+ }
568
+ catch (err) {
569
+ return { ok: false, error: String(err?.message ?? err) };
570
+ }
571
+ }
572
+ /**
573
+ * List open tabs in the current browser context.
574
+ */
575
+ async listTabs() {
576
+ const context = this.page?.context?.();
577
+ if (!context || typeof context.pages !== 'function') {
578
+ return { ok: false, tabs: [], error: 'unsupported_capability' };
579
+ }
580
+ this.pruneTabs();
581
+ const pages = context.pages();
582
+ const tabs = [];
583
+ for (const page of pages) {
584
+ const tab_id = this.ensureTabId(page);
585
+ let title = null;
586
+ try {
587
+ title = await page.title();
588
+ }
589
+ catch {
590
+ title = null;
591
+ }
592
+ let url = null;
593
+ try {
594
+ url = page.url();
595
+ }
596
+ catch {
597
+ url = null;
598
+ }
599
+ tabs.push({ tab_id, url, title, is_active: page === this.page });
600
+ }
601
+ return { ok: true, tabs };
602
+ }
603
+ /**
604
+ * Open a new tab and navigate to the URL.
605
+ */
606
+ async openTab(url) {
607
+ const context = this.page?.context?.();
608
+ if (!context || typeof context.newPage !== 'function') {
609
+ return { ok: false, error: 'unsupported_capability' };
610
+ }
611
+ this.pruneTabs();
612
+ try {
613
+ const page = await context.newPage();
614
+ await page.goto(url);
615
+ this.page = page;
616
+ const tab_id = this.ensureTabId(page);
617
+ let title = null;
618
+ try {
619
+ title = await page.title();
620
+ }
621
+ catch {
622
+ title = null;
623
+ }
624
+ return { ok: true, tab: { tab_id, url: page.url?.() ?? url, title, is_active: true } };
625
+ }
626
+ catch (err) {
627
+ return { ok: false, error: String(err?.message ?? err) };
628
+ }
629
+ }
630
+ /**
631
+ * Switch to an existing tab by id.
632
+ */
633
+ async switchTab(tab_id) {
634
+ this.pruneTabs();
635
+ const page = this.tabRegistry.get(tab_id);
636
+ if (!page) {
637
+ return { ok: false, error: `unknown tab_id: ${tab_id}` };
638
+ }
639
+ this.page = page;
640
+ try {
641
+ await page.bringToFront();
642
+ }
643
+ catch {
644
+ // best-effort
645
+ }
646
+ let title = null;
647
+ try {
648
+ title = await page.title();
649
+ }
650
+ catch {
651
+ title = null;
652
+ }
653
+ return {
654
+ ok: true,
655
+ tab: { tab_id, url: page.url?.() ?? null, title, is_active: true },
656
+ };
657
+ }
658
+ /**
659
+ * Close a tab by id.
660
+ */
661
+ async closeTab(tab_id) {
662
+ this.pruneTabs();
663
+ const page = this.tabRegistry.get(tab_id);
664
+ if (!page) {
665
+ return { ok: false, error: `unknown tab_id: ${tab_id}` };
666
+ }
667
+ let title = null;
668
+ try {
669
+ title = await page.title();
670
+ }
671
+ catch {
672
+ title = null;
673
+ }
674
+ const wasActive = page === this.page;
675
+ try {
676
+ await page.close();
677
+ }
678
+ catch (err) {
679
+ return { ok: false, error: String(err?.message ?? err) };
680
+ }
681
+ this.tabRegistry.delete(tab_id);
682
+ if (wasActive) {
683
+ const context = page?.context?.();
684
+ const pages = context?.pages?.() ?? [];
685
+ if (pages.length > 0) {
686
+ this.page = pages[0];
687
+ }
688
+ }
689
+ return {
690
+ ok: true,
691
+ tab: { tab_id, url: page.url?.() ?? null, title, is_active: wasActive },
692
+ };
693
+ }
694
+ ensureTabId(page) {
695
+ const existing = this.tabIds.get(page);
696
+ if (existing) {
697
+ return existing;
698
+ }
699
+ const tab_id = `tab-${Date.now()}-${Math.random().toString(16).slice(2)}`;
700
+ this.tabIds.set(page, tab_id);
701
+ this.tabRegistry.set(tab_id, page);
702
+ return tab_id;
703
+ }
704
+ pruneTabs() {
705
+ for (const [tab_id, page] of this.tabRegistry.entries()) {
706
+ try {
707
+ const isClosed = page.isClosed?.();
708
+ if (isClosed) {
709
+ this.tabRegistry.delete(tab_id);
710
+ }
711
+ }
712
+ catch {
713
+ // ignore
714
+ }
715
+ }
716
+ }
717
+ isCaptchaDetected(snapshot) {
718
+ const options = this.captchaOptions;
719
+ if (!options) {
720
+ return false;
721
+ }
722
+ const captcha = snapshot.diagnostics?.captcha;
723
+ if (!captcha || !captcha.detected) {
724
+ return false;
725
+ }
726
+ // Many pages load CAPTCHA libraries proactively. Only block when we have
727
+ // evidence it's actually present/active (iframe/url/text hits), otherwise
728
+ // interactive runs can "do nothing" and time out.
729
+ const evidence = captcha.evidence;
730
+ const iframeHits = evidence?.iframe_src_hits ?? [];
731
+ const urlHits = evidence?.url_hits ?? [];
732
+ const textHits = evidence?.text_hits ?? [];
733
+ const selectorHits = evidence?.selector_hits ?? [];
734
+ if (iframeHits.length === 0 && urlHits.length === 0 && textHits.length === 0) {
735
+ return false;
736
+ }
737
+ // Heuristic: many sites include passive reCAPTCHA badges (v3) that should not block.
738
+ // Only block when there is evidence of an interactive challenge.
739
+ const hitsAll = [...iframeHits, ...urlHits, ...textHits, ...selectorHits];
740
+ const hitsLower = hitsAll.map(hit => String(hit || '').toLowerCase()).filter(Boolean);
741
+ const joinedHits = hitsLower.join(' ');
742
+ const strongText = [
743
+ "i'm not a robot",
744
+ 'verify you are human',
745
+ 'human verification',
746
+ 'complete the security check',
747
+ 'please verify',
748
+ ].some(needle => joinedHits.includes(needle));
749
+ const strongIframe = hitsLower.some(hit => ['api2/bframe', 'hcaptcha', 'turnstile'].some(needle => hit.includes(needle)));
750
+ const strongSelector = hitsLower.some(hit => [
751
+ 'g-recaptcha-response',
752
+ 'h-captcha-response',
753
+ 'cf-turnstile-response',
754
+ 'recaptcha-checkbox',
755
+ 'hcaptcha-checkbox',
756
+ ].some(needle => hit.includes(needle)));
757
+ const onlyGeneric = !strongText &&
758
+ !strongIframe &&
759
+ !strongSelector &&
760
+ hitsLower.length > 0 &&
761
+ hitsLower.every(hit => hit.includes('captcha') || hit.includes('recaptcha'));
762
+ if (onlyGeneric) {
763
+ return false;
764
+ }
765
+ const confidence = captcha.confidence ?? 0;
766
+ const minConfidence = options.minConfidence ?? DEFAULT_CAPTCHA_OPTIONS.minConfidence;
767
+ return confidence >= minConfidence;
768
+ }
769
+ buildCaptchaContext(snapshot, source) {
770
+ return {
771
+ runId: this.tracer.getRunId(),
772
+ stepIndex: this.stepIndex,
773
+ url: snapshot.url,
774
+ source,
775
+ captcha: snapshot.diagnostics?.captcha ?? null,
776
+ evaluateJs: async (code) => {
777
+ const result = await this.evaluateJs({ code });
778
+ if (!result.ok) {
779
+ throw new Error(result.error ?? 'evaluateJs failed');
780
+ }
781
+ return result.value;
782
+ },
783
+ };
784
+ }
785
+ emitCaptchaEvent(reasonCode, details = {}) {
786
+ this.tracer.emit('verification', {
787
+ kind: 'captcha',
788
+ passed: false,
789
+ label: reasonCode,
790
+ details: { reason_code: reasonCode, ...details },
791
+ }, this.stepId || undefined);
792
+ }
793
+ async handleCaptchaIfNeeded(snapshot, source) {
794
+ if (!this.captchaOptions) {
795
+ return;
796
+ }
797
+ if (!this.isCaptchaDetected(snapshot)) {
798
+ return;
799
+ }
800
+ const options = this.captchaOptions;
801
+ const minConfidence = options.minConfidence ?? DEFAULT_CAPTCHA_OPTIONS.minConfidence;
802
+ const captcha = snapshot.diagnostics?.captcha ?? null;
803
+ this.emitCaptchaEvent('captcha_detected', { captcha, min_confidence: minConfidence });
804
+ let resolution;
805
+ if (options.policy === 'callback') {
806
+ if (!options.handler) {
807
+ this.emitCaptchaEvent('captcha_handler_error');
808
+ throw new types_1.CaptchaHandlingError('captcha_handler_error', 'Captcha handler is required for policy="callback".');
809
+ }
810
+ try {
811
+ resolution = await options.handler(this.buildCaptchaContext(snapshot, source));
812
+ }
813
+ catch (err) {
814
+ this.emitCaptchaEvent('captcha_handler_error', { error: String(err?.message || err) });
815
+ throw new types_1.CaptchaHandlingError('captcha_handler_error', 'Captcha handler failed.', {
816
+ error: String(err?.message || err),
817
+ });
818
+ }
819
+ if (!resolution || !resolution.action) {
820
+ this.emitCaptchaEvent('captcha_handler_error');
821
+ throw new types_1.CaptchaHandlingError('captcha_handler_error', 'Captcha handler returned an invalid resolution.');
822
+ }
823
+ }
824
+ else {
825
+ resolution = { action: 'abort' };
826
+ }
827
+ await this.applyCaptchaResolution(resolution, snapshot, source);
828
+ }
829
+ async applyCaptchaResolution(resolution, snapshot, source) {
830
+ const options = this.captchaOptions || DEFAULT_CAPTCHA_OPTIONS;
831
+ if (resolution.action === 'abort') {
832
+ this.emitCaptchaEvent('captcha_policy_abort', { message: resolution.message });
833
+ throw new types_1.CaptchaHandlingError('captcha_policy_abort', resolution.message || 'Captcha detected. Aborting per policy.');
834
+ }
835
+ if (resolution.action === 'retry_new_session') {
836
+ this.captchaRetryCount += 1;
837
+ this.emitCaptchaEvent('captcha_retry_new_session');
838
+ if (this.captchaRetryCount > (options.maxRetriesNewSession ?? 1)) {
839
+ this.emitCaptchaEvent('captcha_retry_exhausted');
840
+ throw new types_1.CaptchaHandlingError('captcha_retry_exhausted', 'Captcha retry_new_session exhausted.');
841
+ }
842
+ const resetSession = this.captchaOptions?.resetSession;
843
+ if (!resetSession) {
844
+ throw new types_1.CaptchaHandlingError('captcha_retry_new_session', 'resetSession callback is required for retry_new_session.');
845
+ }
846
+ await resetSession();
847
+ return;
848
+ }
849
+ if (resolution.action === 'wait_until_cleared') {
850
+ const timeoutMs = resolution.timeoutMs ?? options.timeoutMs ?? DEFAULT_CAPTCHA_OPTIONS.timeoutMs;
851
+ const pollMs = resolution.pollMs ?? options.pollMs ?? DEFAULT_CAPTCHA_OPTIONS.pollMs;
852
+ await this.waitUntilCleared(timeoutMs, pollMs, snapshot, source);
853
+ this.emitCaptchaEvent('captcha_resumed');
854
+ }
855
+ }
856
+ async waitUntilCleared(timeoutMs, pollMs, snapshot, source) {
857
+ const deadline = Date.now() + timeoutMs;
858
+ while (Date.now() <= deadline) {
859
+ await new Promise(res => setTimeout(res, pollMs));
860
+ const next = await this.snapshot({ _skipCaptchaHandling: true });
861
+ if (!this.isCaptchaDetected(next)) {
862
+ this.emitCaptchaEvent('captcha_cleared', { source });
863
+ return;
864
+ }
865
+ }
866
+ this.emitCaptchaEvent('captcha_wait_timeout', { timeout_ms: timeoutMs });
867
+ throw new types_1.CaptchaHandlingError('captcha_wait_timeout', 'Captcha wait_until_cleared timed out.');
868
+ }
869
+ /**
870
+ * Enable failure artifact buffer (Phase 1).
871
+ */
872
+ enableFailureArtifacts(options = {}) {
873
+ this.artifactBuffer = new failure_artifacts_1.FailureArtifactBuffer(this.tracer.getRunId(), options);
874
+ const fps = this.artifactBuffer.getOptions().fps;
875
+ if (fps && fps > 0) {
876
+ const intervalMs = Math.max(1, Math.floor(1000 / fps));
877
+ this.artifactTimer = setInterval(() => {
878
+ this.captureArtifactFrame().catch(() => {
879
+ // best-effort
880
+ });
881
+ }, intervalMs);
882
+ }
883
+ }
884
+ /**
885
+ * Disable failure artifact buffer and stop background capture.
886
+ */
887
+ disableFailureArtifacts() {
888
+ if (this.artifactTimer) {
889
+ clearInterval(this.artifactTimer);
890
+ this.artifactTimer = null;
891
+ }
892
+ }
893
+ /**
894
+ * Record an action in the artifact timeline and capture a frame if enabled.
895
+ */
896
+ async recordAction(action, url) {
897
+ this.lastAction = action;
898
+ if (!this.artifactBuffer) {
899
+ return;
900
+ }
901
+ this.artifactBuffer.recordStep(action, this.stepId, this.stepIndex, url);
902
+ if (this.artifactBuffer.getOptions().captureOnAction) {
903
+ await this.captureArtifactFrame();
904
+ }
905
+ }
906
+ /**
907
+ * Emit a step_end event using TraceEventBuilder.
908
+ */
909
+ emitStepEnd(opts) {
910
+ const goal = this.stepGoal || '';
911
+ const preSnap = this.stepPreSnapshot || this.lastSnapshot;
912
+ const preUrl = this.stepPreUrl || preSnap?.url || this.page?.url?.() || '';
913
+ const postUrl = opts.postUrl || this.page?.url?.() || this.lastSnapshot?.url || preUrl;
914
+ const preDigest = preSnap ? trace_event_builder_1.TraceEventBuilder.buildSnapshotDigest(preSnap) : undefined;
915
+ const postDigest = opts.postSnapshotDigest ||
916
+ (this.lastSnapshot ? trace_event_builder_1.TraceEventBuilder.buildSnapshotDigest(this.lastSnapshot) : undefined);
917
+ const urlChanged = Boolean(preUrl && postUrl && String(preUrl) !== String(postUrl));
918
+ const assertionsData = this.getAssertionsForStepEnd();
919
+ const signals = { ...(opts.verifySignals || {}) };
920
+ if (signals.url_changed === undefined) {
921
+ signals.url_changed = urlChanged;
922
+ }
923
+ if (opts.error && signals.error === undefined) {
924
+ signals.error = opts.error;
925
+ }
926
+ if (assertionsData.task_done !== undefined) {
927
+ signals.task_done = assertionsData.task_done;
928
+ }
929
+ if (assertionsData.task_done_label) {
930
+ signals.task_done_label = assertionsData.task_done_label;
931
+ }
932
+ const verifyPassed = opts.verifyPassed !== undefined ? opts.verifyPassed : this.requiredAssertionsPassed();
933
+ const execData = {
934
+ success: opts.success !== undefined ? opts.success : verifyPassed,
935
+ action: opts.action || this.lastAction || 'unknown',
936
+ outcome: opts.outcome || '',
937
+ duration_ms: opts.durationMs,
938
+ error: opts.error,
939
+ };
940
+ const verifyData = {
941
+ passed: Boolean(verifyPassed),
942
+ signals,
943
+ };
944
+ const stepEndData = trace_event_builder_1.TraceEventBuilder.buildRuntimeStepEndData({
945
+ stepId: this.stepId || '',
946
+ stepIndex: this.stepIndex,
947
+ goal,
948
+ attempt: opts.attempt ?? 0,
949
+ preUrl,
950
+ postUrl,
951
+ preSnapshotDigest: preDigest,
952
+ postSnapshotDigest: postDigest,
953
+ execData,
954
+ verifyData,
955
+ assertions: assertionsData.assertions,
956
+ taskDone: assertionsData.task_done,
957
+ taskDoneLabel: assertionsData.task_done_label,
958
+ });
959
+ this.tracer.emit('step_end', stepEndData, this.stepId || undefined);
960
+ return stepEndData;
961
+ }
962
+ /**
963
+ * User-friendly alias for emitStepEnd().
964
+ *
965
+ * Keeps step lifecycle naming symmetric with beginStep().
966
+ */
967
+ endStep(opts = {}) {
968
+ return this.emitStepEnd(opts);
969
+ }
970
+ async captureArtifactFrame() {
971
+ if (!this.artifactBuffer) {
972
+ return;
973
+ }
974
+ try {
975
+ const image = await this.page.screenshot({ type: 'jpeg', quality: 80 });
976
+ await this.artifactBuffer.addFrame(image, 'jpeg');
977
+ }
978
+ catch {
979
+ // best-effort
980
+ }
981
+ }
982
+ /**
983
+ * Finalize artifact buffer at end of run.
984
+ */
985
+ async finalizeRun(success) {
986
+ if (!this.artifactBuffer) {
987
+ return;
988
+ }
989
+ if (success) {
990
+ if (this.artifactBuffer.getOptions().persistMode === 'always') {
991
+ await this.artifactBuffer.persist('success', 'success', this.lastSnapshot ?? undefined, this.lastSnapshot?.diagnostics, this.artifactMetadata());
992
+ }
993
+ await this.artifactBuffer.cleanup();
994
+ }
995
+ else {
996
+ await this.persistFailureArtifacts('finalize_failure');
997
+ }
998
+ }
999
+ async persistFailureArtifacts(reason) {
1000
+ if (!this.artifactBuffer) {
1001
+ return;
1002
+ }
1003
+ await this.artifactBuffer.persist(reason, 'failure', this.lastSnapshot ?? undefined, this.lastSnapshot?.diagnostics, this.artifactMetadata());
1004
+ await this.artifactBuffer.cleanup();
1005
+ if (this.artifactBuffer.getOptions().persistMode === 'onFail') {
1006
+ this.disableFailureArtifacts();
1007
+ }
1008
+ }
1009
+ artifactMetadata() {
1010
+ const url = this.lastSnapshot?.url ?? this.page?.url?.();
1011
+ return {
1012
+ backend: 'playwright',
1013
+ url,
1014
+ };
1015
+ }
1016
+ /**
1017
+ * Begin a new step in the verification loop.
1018
+ *
1019
+ * This:
1020
+ * - Generates a new stepId
1021
+ * - Clears assertions from previous step
1022
+ * - Increments stepIndex (or uses provided value)
1023
+ * - Emits step_start trace event (optional)
1024
+ *
1025
+ * @param goal - Description of what this step aims to achieve
1026
+ * @param stepIndex - Optional explicit step index (otherwise auto-increments)
1027
+ * @param options - Optional settings: emitTrace (default true), preUrl
1028
+ * @returns Generated stepId in format 'step-N' where N is the step index
1029
+ */
1030
+ beginStep(goal, stepIndex, options) {
1031
+ const { emitTrace = true, preUrl } = options || {};
1032
+ // Clear previous step state
1033
+ this.assertionsThisStep = [];
1034
+ this.stepPreSnapshot = null;
1035
+ this.stepPreUrl = null;
1036
+ this.stepGoal = goal;
1037
+ this.lastAction = null;
1038
+ // Update step index
1039
+ if (stepIndex !== undefined) {
1040
+ this.stepIndex = stepIndex;
1041
+ }
1042
+ else {
1043
+ this.stepIndex += 1;
1044
+ }
1045
+ // Generate stepId in 'step-N' format for Studio compatibility
1046
+ this.stepId = `step-${this.stepIndex}`;
1047
+ // Emit step_start trace event for Studio timeline display
1048
+ if (emitTrace && this.tracer) {
1049
+ try {
1050
+ const url = preUrl || this.lastSnapshot?.url || this.page?.url?.() || '';
1051
+ this.tracer.emitStepStart(this.stepId, this.stepIndex, goal, 0, url);
1052
+ }
1053
+ catch {
1054
+ // Tracing must be non-fatal
1055
+ }
1056
+ }
1057
+ return this.stepId;
1058
+ }
1059
+ /**
1060
+ * Evaluate an assertion against current snapshot state.
1061
+ *
1062
+ * The assertion result is:
1063
+ * 1. Accumulated for inclusion in step_end.data.verify.signals.assertions
1064
+ * 2. Emitted as a dedicated 'verification' event for Studio timeline
1065
+ *
1066
+ * @param predicate - Predicate function to evaluate
1067
+ * @param label - Human-readable label for this assertion
1068
+ * @param required - If true, this assertion gates step success (default: false)
1069
+ * @returns True if assertion passed, false otherwise
1070
+ */
1071
+ assert(predicate, label, required = false) {
1072
+ const outcome = predicate(this.ctx());
1073
+ this._recordOutcome(outcome, label, required, null, true);
1074
+ if (required && !outcome.passed) {
1075
+ this.persistFailureArtifacts(`assert_failed:${label}`).catch(() => {
1076
+ // best-effort
1077
+ });
1078
+ }
1079
+ return outcome.passed;
1080
+ }
1081
+ /**
1082
+ * Assert task completion (required assertion).
1083
+ *
1084
+ * This is a convenience wrapper for assert() with required=true.
1085
+ * When the assertion passes, it marks the task as done.
1086
+ *
1087
+ * Use this for final verification that the agent's goal is complete.
1088
+ *
1089
+ * @param predicate - Predicate function to evaluate
1090
+ * @param label - Human-readable label for this assertion
1091
+ * @returns True if task is complete (assertion passed), false otherwise
1092
+ */
1093
+ assertDone(predicate, label) {
1094
+ const ok = this.assert(predicate, label, true);
1095
+ if (ok) {
1096
+ this.taskDone = true;
1097
+ this.taskDoneLabel = label;
1098
+ // Emit task_done verification event
1099
+ this.tracer.emit('verification', {
1100
+ kind: 'task_done',
1101
+ passed: true,
1102
+ label,
1103
+ }, this.stepId || undefined);
1104
+ }
1105
+ return ok;
1106
+ }
1107
+ /**
1108
+ * Get assertions data for inclusion in step_end.data.verify.signals.
1109
+ *
1110
+ * This is called when building the step_end event to include
1111
+ * assertion results in the trace.
1112
+ *
1113
+ * @returns Object with 'assertions', 'task_done', 'task_done_label' keys
1114
+ */
1115
+ getAssertionsForStepEnd() {
1116
+ const result = {
1117
+ assertions: [...this.assertionsThisStep],
1118
+ };
1119
+ if (this.taskDone) {
1120
+ result.task_done = true;
1121
+ result.task_done_label = this.taskDoneLabel || undefined;
1122
+ }
1123
+ return result;
1124
+ }
1125
+ /**
1126
+ * Get and clear assertions for current step.
1127
+ *
1128
+ * Call this at step end to get accumulated assertions
1129
+ * for the step_end event, then clear for next step.
1130
+ *
1131
+ * @returns List of assertion records from this step
1132
+ */
1133
+ flushAssertions() {
1134
+ const assertions = [...this.assertionsThisStep];
1135
+ this.assertionsThisStep = [];
1136
+ return assertions;
1137
+ }
1138
+ /**
1139
+ * Check if task has been marked as done via assertDone().
1140
+ */
1141
+ get isTaskDone() {
1142
+ return this.taskDone;
1143
+ }
1144
+ /**
1145
+ * Reset task_done state (for multi-task runs).
1146
+ */
1147
+ resetTaskDone() {
1148
+ this.taskDone = false;
1149
+ this.taskDoneLabel = null;
1150
+ }
1151
+ /**
1152
+ * Check if all assertions in current step passed.
1153
+ *
1154
+ * @returns True if all assertions passed (or no assertions made)
1155
+ */
1156
+ allAssertionsPassed() {
1157
+ return this.assertionsThisStep.every(a => a.passed);
1158
+ }
1159
+ /**
1160
+ * Check if all required assertions in current step passed.
1161
+ *
1162
+ * @returns True if all required assertions passed (or no required assertions)
1163
+ */
1164
+ requiredAssertionsPassed() {
1165
+ const required = this.assertionsThisStep.filter(a => a.required);
1166
+ return required.every(a => a.passed);
1167
+ }
1168
+ }
1169
+ exports.AgentRuntime = AgentRuntime;
1170
+ //# sourceMappingURL=agent-runtime.js.map