@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.
- package/LICENSE +24 -0
- package/README.md +252 -0
- package/dist/actions.d.ts +185 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +1120 -0
- package/dist/actions.js.map +1 -0
- package/dist/agent-runtime.d.ts +352 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/agent-runtime.js +1170 -0
- package/dist/agent-runtime.js.map +1 -0
- package/dist/agent.d.ts +164 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +408 -0
- package/dist/agent.js.map +1 -0
- package/dist/asserts/expect.d.ts +159 -0
- package/dist/asserts/expect.d.ts.map +1 -0
- package/dist/asserts/expect.js +547 -0
- package/dist/asserts/expect.js.map +1 -0
- package/dist/asserts/index.d.ts +58 -0
- package/dist/asserts/index.d.ts.map +1 -0
- package/dist/asserts/index.js +70 -0
- package/dist/asserts/index.js.map +1 -0
- package/dist/asserts/query.d.ts +199 -0
- package/dist/asserts/query.d.ts.map +1 -0
- package/dist/asserts/query.js +288 -0
- package/dist/asserts/query.js.map +1 -0
- package/dist/backends/actions.d.ts +119 -0
- package/dist/backends/actions.d.ts.map +1 -0
- package/dist/backends/actions.js +291 -0
- package/dist/backends/actions.js.map +1 -0
- package/dist/backends/browser-use-adapter.d.ts +131 -0
- package/dist/backends/browser-use-adapter.d.ts.map +1 -0
- package/dist/backends/browser-use-adapter.js +219 -0
- package/dist/backends/browser-use-adapter.js.map +1 -0
- package/dist/backends/cdp-backend.d.ts +66 -0
- package/dist/backends/cdp-backend.d.ts.map +1 -0
- package/dist/backends/cdp-backend.js +273 -0
- package/dist/backends/cdp-backend.js.map +1 -0
- package/dist/backends/index.d.ts +80 -0
- package/dist/backends/index.d.ts.map +1 -0
- package/dist/backends/index.js +101 -0
- package/dist/backends/index.js.map +1 -0
- package/dist/backends/protocol.d.ts +156 -0
- package/dist/backends/protocol.d.ts.map +1 -0
- package/dist/backends/protocol.js +16 -0
- package/dist/backends/protocol.js.map +1 -0
- package/dist/backends/sentience-context.d.ts +143 -0
- package/dist/backends/sentience-context.d.ts.map +1 -0
- package/dist/backends/sentience-context.js +359 -0
- package/dist/backends/sentience-context.js.map +1 -0
- package/dist/backends/snapshot.d.ts +188 -0
- package/dist/backends/snapshot.d.ts.map +1 -0
- package/dist/backends/snapshot.js +360 -0
- package/dist/backends/snapshot.js.map +1 -0
- package/dist/browser.d.ts +154 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +920 -0
- package/dist/browser.js.map +1 -0
- package/dist/canonicalization.d.ts +126 -0
- package/dist/canonicalization.d.ts.map +1 -0
- package/dist/canonicalization.js +161 -0
- package/dist/canonicalization.js.map +1 -0
- package/dist/captcha/strategies.d.ts +12 -0
- package/dist/captcha/strategies.d.ts.map +1 -0
- package/dist/captcha/strategies.js +43 -0
- package/dist/captcha/strategies.js.map +1 -0
- package/dist/captcha/types.d.ts +45 -0
- package/dist/captcha/types.d.ts.map +1 -0
- package/dist/captcha/types.js +12 -0
- package/dist/captcha/types.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +422 -0
- package/dist/cli.js.map +1 -0
- package/dist/conversational-agent.d.ts +123 -0
- package/dist/conversational-agent.d.ts.map +1 -0
- package/dist/conversational-agent.js +341 -0
- package/dist/conversational-agent.js.map +1 -0
- package/dist/cursor-policy.d.ts +41 -0
- package/dist/cursor-policy.d.ts.map +1 -0
- package/dist/cursor-policy.js +81 -0
- package/dist/cursor-policy.js.map +1 -0
- package/dist/debugger.d.ts +28 -0
- package/dist/debugger.d.ts.map +1 -0
- package/dist/debugger.js +107 -0
- package/dist/debugger.js.map +1 -0
- package/dist/expect.d.ts +16 -0
- package/dist/expect.d.ts.map +1 -0
- package/dist/expect.js +67 -0
- package/dist/expect.js.map +1 -0
- package/dist/failure-artifacts.d.ts +95 -0
- package/dist/failure-artifacts.d.ts.map +1 -0
- package/dist/failure-artifacts.js +805 -0
- package/dist/failure-artifacts.js.map +1 -0
- package/dist/generator.d.ts +16 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +205 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +160 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +13 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +153 -0
- package/dist/inspector.js.map +1 -0
- package/dist/llm-provider.d.ts +144 -0
- package/dist/llm-provider.d.ts.map +1 -0
- package/dist/llm-provider.js +460 -0
- package/dist/llm-provider.js.map +1 -0
- package/dist/ordinal.d.ts +90 -0
- package/dist/ordinal.d.ts.map +1 -0
- package/dist/ordinal.js +249 -0
- package/dist/ordinal.js.map +1 -0
- package/dist/overlay.d.ts +63 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +102 -0
- package/dist/overlay.js.map +1 -0
- package/dist/protocols/browser-protocol.d.ts +79 -0
- package/dist/protocols/browser-protocol.d.ts.map +1 -0
- package/dist/protocols/browser-protocol.js +9 -0
- package/dist/protocols/browser-protocol.js.map +1 -0
- package/dist/query.d.ts +66 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +482 -0
- package/dist/query.js.map +1 -0
- package/dist/read.d.ts +47 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +128 -0
- package/dist/read.js.map +1 -0
- package/dist/recorder.d.ts +44 -0
- package/dist/recorder.d.ts.map +1 -0
- package/dist/recorder.js +262 -0
- package/dist/recorder.js.map +1 -0
- package/dist/runtime-agent.d.ts +72 -0
- package/dist/runtime-agent.d.ts.map +1 -0
- package/dist/runtime-agent.js +357 -0
- package/dist/runtime-agent.js.map +1 -0
- package/dist/screenshot.d.ts +17 -0
- package/dist/screenshot.d.ts.map +1 -0
- package/dist/screenshot.js +40 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/snapshot-diff.d.ts +23 -0
- package/dist/snapshot-diff.d.ts.map +1 -0
- package/dist/snapshot-diff.js +119 -0
- package/dist/snapshot-diff.js.map +1 -0
- package/dist/snapshot.d.ts +47 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +358 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/textSearch.d.ts +64 -0
- package/dist/textSearch.d.ts.map +1 -0
- package/dist/textSearch.js +113 -0
- package/dist/textSearch.js.map +1 -0
- package/dist/tools/context.d.ts +18 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +40 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/defaults.d.ts +5 -0
- package/dist/tools/defaults.d.ts.map +1 -0
- package/dist/tools/defaults.js +368 -0
- package/dist/tools/defaults.js.map +1 -0
- package/dist/tools/filesystem.d.ts +12 -0
- package/dist/tools/filesystem.d.ts.map +1 -0
- package/dist/tools/filesystem.js +137 -0
- package/dist/tools/filesystem.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +38 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +100 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tracing/cloud-sink.d.ts +189 -0
- package/dist/tracing/cloud-sink.d.ts.map +1 -0
- package/dist/tracing/cloud-sink.js +1067 -0
- package/dist/tracing/cloud-sink.js.map +1 -0
- package/dist/tracing/index-schema.d.ts +231 -0
- package/dist/tracing/index-schema.d.ts.map +1 -0
- package/dist/tracing/index-schema.js +235 -0
- package/dist/tracing/index-schema.js.map +1 -0
- package/dist/tracing/index.d.ts +12 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +28 -0
- package/dist/tracing/index.js.map +1 -0
- package/dist/tracing/indexer.d.ts +20 -0
- package/dist/tracing/indexer.d.ts.map +1 -0
- package/dist/tracing/indexer.js +347 -0
- package/dist/tracing/indexer.js.map +1 -0
- package/dist/tracing/jsonl-sink.d.ts +51 -0
- package/dist/tracing/jsonl-sink.d.ts.map +1 -0
- package/dist/tracing/jsonl-sink.js +329 -0
- package/dist/tracing/jsonl-sink.js.map +1 -0
- package/dist/tracing/sink.d.ts +25 -0
- package/dist/tracing/sink.d.ts.map +1 -0
- package/dist/tracing/sink.js +15 -0
- package/dist/tracing/sink.js.map +1 -0
- package/dist/tracing/tracer-factory.d.ts +102 -0
- package/dist/tracing/tracer-factory.d.ts.map +1 -0
- package/dist/tracing/tracer-factory.js +375 -0
- package/dist/tracing/tracer-factory.js.map +1 -0
- package/dist/tracing/tracer.d.ts +140 -0
- package/dist/tracing/tracer.d.ts.map +1 -0
- package/dist/tracing/tracer.js +336 -0
- package/dist/tracing/tracer.js.map +1 -0
- package/dist/tracing/types.d.ts +203 -0
- package/dist/tracing/types.d.ts.map +1 -0
- package/dist/tracing/types.js +8 -0
- package/dist/tracing/types.js.map +1 -0
- package/dist/types.d.ts +422 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/action-executor.d.ts +25 -0
- package/dist/utils/action-executor.d.ts.map +1 -0
- package/dist/utils/action-executor.js +121 -0
- package/dist/utils/action-executor.js.map +1 -0
- package/dist/utils/browser-evaluator.d.ts +76 -0
- package/dist/utils/browser-evaluator.d.ts.map +1 -0
- package/dist/utils/browser-evaluator.js +130 -0
- package/dist/utils/browser-evaluator.js.map +1 -0
- package/dist/utils/browser.d.ts +30 -0
- package/dist/utils/browser.d.ts.map +1 -0
- package/dist/utils/browser.js +75 -0
- package/dist/utils/browser.js.map +1 -0
- package/dist/utils/element-filter.d.ts +76 -0
- package/dist/utils/element-filter.d.ts.map +1 -0
- package/dist/utils/element-filter.js +195 -0
- package/dist/utils/element-filter.js.map +1 -0
- package/dist/utils/grid-utils.d.ts +37 -0
- package/dist/utils/grid-utils.d.ts.map +1 -0
- package/dist/utils/grid-utils.js +283 -0
- package/dist/utils/grid-utils.js.map +1 -0
- package/dist/utils/llm-interaction-handler.d.ts +41 -0
- package/dist/utils/llm-interaction-handler.d.ts.map +1 -0
- package/dist/utils/llm-interaction-handler.js +171 -0
- package/dist/utils/llm-interaction-handler.js.map +1 -0
- package/dist/utils/llm-response-builder.d.ts +56 -0
- package/dist/utils/llm-response-builder.d.ts.map +1 -0
- package/dist/utils/llm-response-builder.js +130 -0
- package/dist/utils/llm-response-builder.js.map +1 -0
- package/dist/utils/selector-utils.d.ts +12 -0
- package/dist/utils/selector-utils.d.ts.map +1 -0
- package/dist/utils/selector-utils.js +32 -0
- package/dist/utils/selector-utils.js.map +1 -0
- package/dist/utils/snapshot-event-builder.d.ts +28 -0
- package/dist/utils/snapshot-event-builder.d.ts.map +1 -0
- package/dist/utils/snapshot-event-builder.js +88 -0
- package/dist/utils/snapshot-event-builder.js.map +1 -0
- package/dist/utils/snapshot-processor.d.ts +27 -0
- package/dist/utils/snapshot-processor.d.ts.map +1 -0
- package/dist/utils/snapshot-processor.js +47 -0
- package/dist/utils/snapshot-processor.js.map +1 -0
- package/dist/utils/trace-event-builder.d.ts +122 -0
- package/dist/utils/trace-event-builder.d.ts.map +1 -0
- package/dist/utils/trace-event-builder.js +365 -0
- package/dist/utils/trace-event-builder.js.map +1 -0
- package/dist/utils/trace-file-manager.d.ts +70 -0
- package/dist/utils/trace-file-manager.d.ts.map +1 -0
- package/dist/utils/trace-file-manager.js +194 -0
- package/dist/utils/trace-file-manager.js.map +1 -0
- package/dist/utils/zod.d.ts +5 -0
- package/dist/utils/zod.d.ts.map +1 -0
- package/dist/utils/zod.js +80 -0
- package/dist/utils/zod.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +13 -0
- package/dist/utils.js.map +1 -0
- package/dist/verification.d.ts +194 -0
- package/dist/verification.d.ts.map +1 -0
- package/dist/verification.js +530 -0
- package/dist/verification.js.map +1 -0
- package/dist/vision-executor.d.ts +18 -0
- package/dist/vision-executor.d.ts.map +1 -0
- package/dist/vision-executor.js +60 -0
- package/dist/vision-executor.js.map +1 -0
- package/dist/visual-agent.d.ts +120 -0
- package/dist/visual-agent.d.ts.map +1 -0
- package/dist/visual-agent.js +796 -0
- package/dist/visual-agent.js.map +1 -0
- package/dist/wait.d.ts +35 -0
- package/dist/wait.d.ts.map +1 -0
- package/dist/wait.js +76 -0
- package/dist/wait.js.map +1 -0
- package/package.json +94 -0
- package/spec/README.md +72 -0
- package/spec/SNAPSHOT_V1.md +208 -0
- package/spec/sdk-types.md +259 -0
- package/spec/snapshot.schema.json +148 -0
- package/src/extension/background.js +104 -0
- package/src/extension/content.js +162 -0
- package/src/extension/injected_api.js +1399 -0
- package/src/extension/manifest.json +36 -0
- package/src/extension/pkg/README.md +1340 -0
- package/src/extension/pkg/package.json +15 -0
- package/src/extension/pkg/sentience_core.d.ts +51 -0
- package/src/extension/pkg/sentience_core.js +371 -0
- package/src/extension/pkg/sentience_core_bg.wasm +0 -0
- package/src/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
- package/src/extension/release.json +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
This project is dual-licensed under your choice of either:
|
|
4
|
+
|
|
5
|
+
* **MIT License** ([LICENSE-MIT](./LICENSE-MIT))
|
|
6
|
+
* **Apache License 2.0** ([LICENSE-APACHE](./LICENSE-APACHE))
|
|
7
|
+
|
|
8
|
+
## Choosing a License
|
|
9
|
+
|
|
10
|
+
You may use this software under the terms of either license, at your option.
|
|
11
|
+
|
|
12
|
+
### MIT License
|
|
13
|
+
The MIT License is a permissive license that is short and to the point. It lets people do almost anything they want with your project, like making and distributing closed source versions.
|
|
14
|
+
|
|
15
|
+
### Apache License 2.0
|
|
16
|
+
The Apache License 2.0 is also a permissive license, similar to MIT, but it also provides an express grant of patent rights from contributors to users.
|
|
17
|
+
|
|
18
|
+
## Contribution
|
|
19
|
+
|
|
20
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you shall be dual-licensed as above, without any additional terms or conditions.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
Copyright (c) 2025 Sentience Contributors
|
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Predicate TypeScript SDK
|
|
2
|
+
|
|
3
|
+
> **A verification & control layer for AI agents that operate browsers**
|
|
4
|
+
|
|
5
|
+
Predicate is built for **AI agent developers** who already use Playwright / CDP / LangGraph and care about **flakiness, cost, determinism, evals, and debugging**.
|
|
6
|
+
|
|
7
|
+
Often described as _Jest for Browser AI Agents_ - but applied to end-to-end agent runs (not unit tests).
|
|
8
|
+
|
|
9
|
+
The core loop is:
|
|
10
|
+
|
|
11
|
+
> **Agent → Snapshot → Action → Verification → Artifact**
|
|
12
|
+
|
|
13
|
+
## What Predicate is
|
|
14
|
+
|
|
15
|
+
- A **verification-first runtime** (`AgentRuntime`) for browser agents
|
|
16
|
+
- Treats the browser as an adapter (Playwright / CDP); **`AgentRuntime` is the product**
|
|
17
|
+
- A **controlled perception** layer (semantic snapshots; pruning/limits; lowers token usage by filtering noise from what models see)
|
|
18
|
+
- A **debugging layer** (structured traces + failure artifacts)
|
|
19
|
+
- Enables **local LLM small models (3B-7B)** for browser automation (privacy, compliance, and cost control)
|
|
20
|
+
- Keeps vision models **optional** (use as a fallback when DOM/snapshot structure falls short, e.g. `<canvas>`)
|
|
21
|
+
|
|
22
|
+
## What Predicate is not
|
|
23
|
+
|
|
24
|
+
- Not a browser driver
|
|
25
|
+
- Not a Playwright replacement
|
|
26
|
+
- Not a vision-first agent framework
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @predicatelabs/sdk
|
|
32
|
+
npx playwright install chromium
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Naming migration (Predicate rebrand)
|
|
36
|
+
|
|
37
|
+
Use the new `Predicate*` class names for all new code:
|
|
38
|
+
|
|
39
|
+
- `PredicateBrowser`
|
|
40
|
+
- `PredicateAgent`
|
|
41
|
+
- `PredicateVisualAgent`
|
|
42
|
+
- `PredicateDebugger`
|
|
43
|
+
- `backends.PredicateContext`
|
|
44
|
+
|
|
45
|
+
## Conceptual example (why this exists)
|
|
46
|
+
|
|
47
|
+
- Steps are **gated by verifiable UI assertions**
|
|
48
|
+
- If progress can’t be proven, the run **fails with evidence**
|
|
49
|
+
- This is how you make runs **reproducible** and **debuggable**, and how you run evals reliably
|
|
50
|
+
|
|
51
|
+
## Quickstart: a verification-first loop
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { PredicateBrowser, AgentRuntime } from '@predicatelabs/sdk';
|
|
55
|
+
import { JsonlTraceSink, Tracer } from '@predicatelabs/sdk';
|
|
56
|
+
import { exists, urlContains } from '@predicatelabs/sdk';
|
|
57
|
+
import type { Page } from 'playwright';
|
|
58
|
+
|
|
59
|
+
async function main(): Promise<void> {
|
|
60
|
+
const tracer = new Tracer('demo', new JsonlTraceSink('trace.jsonl'));
|
|
61
|
+
|
|
62
|
+
const browser = new PredicateBrowser();
|
|
63
|
+
await browser.start();
|
|
64
|
+
const page = browser.getPage();
|
|
65
|
+
if (!page) throw new Error('no page');
|
|
66
|
+
|
|
67
|
+
await page.goto('https://example.com');
|
|
68
|
+
|
|
69
|
+
// AgentRuntime needs a snapshot provider; PredicateBrowser.snapshot() does not depend on Page,
|
|
70
|
+
// so we wrap it to fit the runtime interface.
|
|
71
|
+
const runtime = new AgentRuntime(
|
|
72
|
+
{ snapshot: async (_page: Page, options?: Record<string, any>) => browser.snapshot(options) },
|
|
73
|
+
page,
|
|
74
|
+
tracer
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
runtime.beginStep('Verify homepage');
|
|
78
|
+
await runtime.snapshot({ limit: 60 });
|
|
79
|
+
|
|
80
|
+
runtime.assert(urlContains('example.com'), 'on_domain', true);
|
|
81
|
+
runtime.assert(exists('role=heading'), 'has_heading');
|
|
82
|
+
|
|
83
|
+
runtime.assertDone(exists("text~'Example'"), 'task_complete');
|
|
84
|
+
|
|
85
|
+
await browser.close();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void main();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## PredicateDebugger: attach to your existing agent framework (sidecar mode)
|
|
92
|
+
|
|
93
|
+
If you already have an agent loop (LangGraph, custom planner/executor), keep it and attach Predicate as a **verifier + trace layer**.
|
|
94
|
+
|
|
95
|
+
Key idea: your agent still executes actions — Predicate **snapshots and verifies outcomes**.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import type { Page } from 'playwright';
|
|
99
|
+
import { PredicateDebugger, Tracer, JsonlTraceSink, exists, urlContains } from '@predicatelabs/sdk';
|
|
100
|
+
|
|
101
|
+
async function runExistingAgent(page: Page): Promise<void> {
|
|
102
|
+
const tracer = new Tracer('run-123', new JsonlTraceSink('trace.jsonl'));
|
|
103
|
+
const dbg = PredicateDebugger.attach(page, tracer);
|
|
104
|
+
|
|
105
|
+
await dbg.step('agent_step: navigate + verify', async () => {
|
|
106
|
+
// 1) Let your framework do whatever it does
|
|
107
|
+
await yourAgent.step();
|
|
108
|
+
|
|
109
|
+
// 2) Snapshot what the agent produced
|
|
110
|
+
await dbg.snapshot({ limit: 60 });
|
|
111
|
+
|
|
112
|
+
// 3) Verify outcomes (with bounded retries)
|
|
113
|
+
await dbg
|
|
114
|
+
.check(urlContains('example.com'), 'on_domain', true)
|
|
115
|
+
.eventually({ timeoutMs: 10_000 });
|
|
116
|
+
await dbg.check(exists('role=heading'), 'has_heading').eventually({ timeoutMs: 10_000 });
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## SDK-driven full loop (snapshots + actions)
|
|
122
|
+
|
|
123
|
+
If you want Predicate to drive the loop end-to-end, you can use the SDK primitives directly: take a snapshot, select elements, act, then verify.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { PredicateBrowser, snapshot, find, typeText, click, waitFor } from '@predicatelabs/sdk';
|
|
127
|
+
|
|
128
|
+
async function loginExample(): Promise<void> {
|
|
129
|
+
const browser = new PredicateBrowser();
|
|
130
|
+
await browser.start();
|
|
131
|
+
const page = browser.getPage();
|
|
132
|
+
if (!page) throw new Error('no page');
|
|
133
|
+
|
|
134
|
+
await page.goto('https://example.com/login');
|
|
135
|
+
|
|
136
|
+
const snap = await snapshot(browser);
|
|
137
|
+
const email = find(snap, "role=textbox text~'email'");
|
|
138
|
+
const password = find(snap, "role=textbox text~'password'");
|
|
139
|
+
const submit = find(snap, "role=button text~'sign in'");
|
|
140
|
+
if (!email || !password || !submit) throw new Error('login form not found');
|
|
141
|
+
|
|
142
|
+
await typeText(browser, email.id, 'user@example.com');
|
|
143
|
+
await typeText(browser, password.id, 'password123');
|
|
144
|
+
await click(browser, submit.id);
|
|
145
|
+
|
|
146
|
+
const ok = await waitFor(browser, "role=heading text~'Dashboard'", 10_000);
|
|
147
|
+
if (!ok.found) throw new Error('login failed');
|
|
148
|
+
|
|
149
|
+
await browser.close();
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Capabilities (lifecycle guarantees)
|
|
154
|
+
|
|
155
|
+
### Controlled perception
|
|
156
|
+
|
|
157
|
+
- **Semantic snapshots** instead of raw DOM dumps
|
|
158
|
+
- **Pruning knobs** via `SnapshotOptions` (limit/filter)
|
|
159
|
+
- Snapshot diagnostics that help decide when “structure is insufficient”
|
|
160
|
+
|
|
161
|
+
### Constrained action space
|
|
162
|
+
|
|
163
|
+
- Action primitives operate on **stable IDs / rects** derived from snapshots
|
|
164
|
+
- Optional helpers for ordinality (“click the 3rd result”)
|
|
165
|
+
|
|
166
|
+
### Verified progress
|
|
167
|
+
|
|
168
|
+
- Predicates like `exists(...)`, `urlMatches(...)`, `isEnabled(...)`, `valueEquals(...)`
|
|
169
|
+
- Fluent assertion DSL via `expect(...)`
|
|
170
|
+
- Retrying verification via `runtime.check(...).eventually(...)`
|
|
171
|
+
|
|
172
|
+
### Explained failure
|
|
173
|
+
|
|
174
|
+
- JSONL trace events (`Tracer` + `JsonlTraceSink`)
|
|
175
|
+
- Optional failure artifact bundles (snapshots, diagnostics, step timelines, frames/clip)
|
|
176
|
+
- Deterministic failure semantics: when required assertions can’t be proven, the run fails with artifacts you can replay
|
|
177
|
+
|
|
178
|
+
### Framework interoperability
|
|
179
|
+
|
|
180
|
+
- Bring your own LLM and orchestration (LangGraph, custom loops)
|
|
181
|
+
- Register explicit LLM-callable tools with `ToolRegistry`
|
|
182
|
+
|
|
183
|
+
## ToolRegistry (LLM-callable tools)
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { ToolRegistry, registerDefaultTools } from '@predicatelabs/sdk';
|
|
187
|
+
|
|
188
|
+
const registry = new ToolRegistry();
|
|
189
|
+
registerDefaultTools(registry);
|
|
190
|
+
const toolsForLLM = registry.llmTools();
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Permissions (avoid Chrome permission bubbles)
|
|
194
|
+
|
|
195
|
+
Chrome permission prompts are outside the DOM and can be invisible to snapshots. Prefer setting a policy **before navigation**.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { PredicateBrowser } from '@predicatelabs/sdk';
|
|
199
|
+
import type { PermissionPolicy } from '@predicatelabs/sdk';
|
|
200
|
+
|
|
201
|
+
const policy: PermissionPolicy = {
|
|
202
|
+
default: 'clear',
|
|
203
|
+
autoGrant: ['geolocation'],
|
|
204
|
+
geolocation: { latitude: 37.77, longitude: -122.41, accuracy: 50 },
|
|
205
|
+
origin: 'https://example.com',
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// `permissionPolicy` is the last constructor argument; pass `keepAlive` right before it.
|
|
209
|
+
const browser = new PredicateBrowser(
|
|
210
|
+
undefined,
|
|
211
|
+
undefined,
|
|
212
|
+
undefined,
|
|
213
|
+
undefined,
|
|
214
|
+
undefined,
|
|
215
|
+
undefined,
|
|
216
|
+
undefined,
|
|
217
|
+
undefined,
|
|
218
|
+
undefined,
|
|
219
|
+
undefined,
|
|
220
|
+
undefined,
|
|
221
|
+
undefined,
|
|
222
|
+
false,
|
|
223
|
+
policy
|
|
224
|
+
);
|
|
225
|
+
await browser.start();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
If your backend supports it, you can also use ToolRegistry permission tools (`grant_permissions`, `clear_permissions`, `set_geolocation`) mid-run.
|
|
229
|
+
|
|
230
|
+
## Downloads (verification predicate)
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { downloadCompleted } from '@predicatelabs/sdk';
|
|
234
|
+
|
|
235
|
+
runtime.assert(downloadCompleted('report.csv'), 'download_ok', true);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Debugging (fast)
|
|
239
|
+
|
|
240
|
+
- **Manual driver CLI**:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
npx predicate driver --url https://example.com
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- **Verification + artifacts + debugging with time-travel traces (Predicate Studio demo)**:
|
|
247
|
+
|
|
248
|
+
<video src="https://github.com/user-attachments/assets/7ffde43b-1074-4d70-bb83-2eb8d0469307" controls muted playsinline></video>
|
|
249
|
+
|
|
250
|
+
If the video tag doesn’t render in your GitHub README view, use this link: [`sentience-studio-demo.mp4`](https://github.com/user-attachments/assets/7ffde43b-1074-4d70-bb83-2eb8d0469307)
|
|
251
|
+
|
|
252
|
+
- **Predicate SDK Documentation**: https://predicatelabs.dev/docs
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actions v1 - click, type, press
|
|
3
|
+
*/
|
|
4
|
+
import { IBrowser } from './protocols/browser-protocol';
|
|
5
|
+
import { ActionResult, BBox } from './types';
|
|
6
|
+
import { SnapshotOptions } from './snapshot';
|
|
7
|
+
import { CursorPolicy } from './cursor-policy';
|
|
8
|
+
export interface ClickRect {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
w?: number;
|
|
12
|
+
width?: number;
|
|
13
|
+
h?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Click an element by its ID
|
|
18
|
+
*
|
|
19
|
+
* Uses a hybrid approach: gets element bounding box from snapshot and calculates center,
|
|
20
|
+
* then uses Playwright's native mouse.click() for realistic event simulation.
|
|
21
|
+
* Falls back to JavaScript click if element not found in snapshot.
|
|
22
|
+
*
|
|
23
|
+
* @param browser - SentienceBrowser instance
|
|
24
|
+
* @param elementId - Element ID from snapshot
|
|
25
|
+
* @param useMouse - Use mouse simulation (default: true). If false, uses JavaScript click.
|
|
26
|
+
* @param takeSnapshot - Take snapshot after action (default: false)
|
|
27
|
+
* @returns ActionResult with success status, outcome, duration, and optional snapshot
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const snap = await snapshot(browser);
|
|
32
|
+
* const button = find(snap, 'role=button');
|
|
33
|
+
* if (button) {
|
|
34
|
+
* const result = await click(browser, button.id);
|
|
35
|
+
* console.log(`Click ${result.success ? 'succeeded' : 'failed'}`);
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function click(browser: IBrowser, elementId: number, useMouse?: boolean, takeSnapshot?: boolean, cursorPolicy?: CursorPolicy): Promise<ActionResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Type text into an input element
|
|
42
|
+
*
|
|
43
|
+
* Focuses the element first, then types the text using Playwright's keyboard simulation.
|
|
44
|
+
*
|
|
45
|
+
* @param browser - SentienceBrowser instance
|
|
46
|
+
* @param elementId - Element ID from snapshot (must be a text input element)
|
|
47
|
+
* @param text - Text to type
|
|
48
|
+
* @param takeSnapshot - Take snapshot after action (default: false)
|
|
49
|
+
* @param delayMs - Delay between keystrokes in milliseconds for human-like typing (default: 0)
|
|
50
|
+
* @returns ActionResult with success status, outcome, duration, and optional snapshot
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const snap = await snapshot(browser);
|
|
55
|
+
* const searchBox = find(snap, 'role=searchbox');
|
|
56
|
+
* if (searchBox) {
|
|
57
|
+
* // Type instantly (default behavior)
|
|
58
|
+
* await typeText(browser, searchBox.id, 'Hello World');
|
|
59
|
+
*
|
|
60
|
+
* // Type with human-like delay (~10ms between keystrokes)
|
|
61
|
+
* await typeText(browser, searchBox.id, 'Hello World', false, 10);
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function typeText(browser: IBrowser, elementId: number, text: string, takeSnapshot?: boolean, delayMs?: number): Promise<ActionResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Clear the value of an input/textarea element (best-effort).
|
|
68
|
+
*/
|
|
69
|
+
export declare function clear(browser: IBrowser, elementId: number, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Ensure a checkbox/radio is checked (best-effort).
|
|
72
|
+
*/
|
|
73
|
+
export declare function check(browser: IBrowser, elementId: number, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Ensure a checkbox/radio is unchecked (best-effort).
|
|
76
|
+
*/
|
|
77
|
+
export declare function uncheck(browser: IBrowser, elementId: number, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Select an option in a <select> element by matching option value or label (best-effort).
|
|
80
|
+
*/
|
|
81
|
+
export declare function selectOption(browser: IBrowser, elementId: number, option: string, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Upload a local file via an <input type="file"> element (best-effort).
|
|
84
|
+
*/
|
|
85
|
+
export declare function uploadFile(browser: IBrowser, elementId: number, filePath: string, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
86
|
+
/**
|
|
87
|
+
* Submit a form (best-effort) by clicking a submit control or calling requestSubmit().
|
|
88
|
+
*/
|
|
89
|
+
export declare function submit(browser: IBrowser, elementId: number, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Navigate back in history (best-effort).
|
|
92
|
+
*/
|
|
93
|
+
export declare function back(browser: IBrowser, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Scroll an element into view
|
|
96
|
+
*
|
|
97
|
+
* Scrolls the page so that the specified element is visible in the viewport.
|
|
98
|
+
* Uses the element registry to find the element and scrollIntoView() to scroll it.
|
|
99
|
+
*
|
|
100
|
+
* @param browser - SentienceBrowser instance
|
|
101
|
+
* @param elementId - Element ID from snapshot to scroll into view
|
|
102
|
+
* @param behavior - Scroll behavior: 'smooth' for animated scroll, 'instant' for immediate (default: 'smooth')
|
|
103
|
+
* @param block - Vertical alignment: 'start', 'center', 'end', 'nearest' (default: 'center')
|
|
104
|
+
* @param takeSnapshot - Take snapshot after action (default: false)
|
|
105
|
+
* @returns ActionResult with success status, outcome, duration, and optional snapshot
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const snap = await snapshot(browser);
|
|
110
|
+
* const button = find(snap, 'role=button[name="Submit"]');
|
|
111
|
+
* if (button) {
|
|
112
|
+
* // Scroll element into view with smooth animation
|
|
113
|
+
* await scrollTo(browser, button.id);
|
|
114
|
+
*
|
|
115
|
+
* // Scroll instantly to top of viewport
|
|
116
|
+
* await scrollTo(browser, button.id, 'instant', 'start');
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare function scrollTo(browser: IBrowser, elementId: number, behavior?: 'smooth' | 'instant' | 'auto', block?: 'start' | 'center' | 'end' | 'nearest', takeSnapshot?: boolean): Promise<ActionResult>;
|
|
121
|
+
/**
|
|
122
|
+
* Press a keyboard key
|
|
123
|
+
*
|
|
124
|
+
* Simulates pressing a key using Playwright's keyboard API.
|
|
125
|
+
* Common keys: 'Enter', 'Escape', 'Tab', 'ArrowUp', 'ArrowDown', etc.
|
|
126
|
+
*
|
|
127
|
+
* @param browser - SentienceBrowser instance
|
|
128
|
+
* @param key - Key to press (e.g., 'Enter', 'Escape', 'Tab')
|
|
129
|
+
* @param takeSnapshot - Take snapshot after action (default: false)
|
|
130
|
+
* @returns ActionResult with success status, outcome, duration, and optional snapshot
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* // Press Enter after typing
|
|
135
|
+
* await typeText(browser, elementId, 'search query');
|
|
136
|
+
* await press(browser, 'Enter');
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function press(browser: IBrowser, key: string, takeSnapshot?: boolean): Promise<ActionResult>;
|
|
140
|
+
/**
|
|
141
|
+
* Send a sequence of key presses (e.g., "CMD+H", "CTRL+SHIFT+P").
|
|
142
|
+
*/
|
|
143
|
+
export declare function sendKeys(browser: IBrowser, sequence: string, takeSnapshot?: boolean, delayMs?: number): Promise<ActionResult>;
|
|
144
|
+
/**
|
|
145
|
+
* Navigate to a search results page for the given query.
|
|
146
|
+
*/
|
|
147
|
+
export declare function search(browser: IBrowser, query: string, engine?: string, takeSnapshot?: boolean, snapshotOptions?: SnapshotOptions | undefined): Promise<ActionResult>;
|
|
148
|
+
/**
|
|
149
|
+
* Click at the center of a rectangle using Playwright's native mouse simulation.
|
|
150
|
+
* This uses a hybrid approach: calculates center coordinates and uses mouse.click()
|
|
151
|
+
* for realistic event simulation (triggers hover, focus, mousedown, mouseup).
|
|
152
|
+
*
|
|
153
|
+
* @param browser - SentienceBrowser instance
|
|
154
|
+
* @param rect - Rectangle with x, y, w (or width), h (or height) keys, or BBox object
|
|
155
|
+
* @param highlight - Whether to show a red border highlight when clicking (default: true)
|
|
156
|
+
* @param highlightDuration - How long to show the highlight in seconds (default: 2.0)
|
|
157
|
+
* @param takeSnapshot - Whether to take snapshot after action
|
|
158
|
+
* @returns ActionResult
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* // Click using rect object
|
|
163
|
+
* await clickRect(browser, { x: 100, y: 200, w: 50, h: 30 });
|
|
164
|
+
*
|
|
165
|
+
* // Click using BBox from element
|
|
166
|
+
* const snap = await snapshot(browser);
|
|
167
|
+
* const element = find(snap, "role=button");
|
|
168
|
+
* if (element) {
|
|
169
|
+
* await clickRect(browser, {
|
|
170
|
+
* x: element.bbox.x,
|
|
171
|
+
* y: element.bbox.y,
|
|
172
|
+
* w: element.bbox.width,
|
|
173
|
+
* h: element.bbox.height
|
|
174
|
+
* });
|
|
175
|
+
* }
|
|
176
|
+
*
|
|
177
|
+
* // Without highlight
|
|
178
|
+
* await clickRect(browser, { x: 100, y: 200, w: 50, h: 30 }, false);
|
|
179
|
+
*
|
|
180
|
+
* // Custom highlight duration
|
|
181
|
+
* await clickRect(browser, { x: 100, y: 200, w: 50, h: 30 }, true, 3.0);
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export declare function clickRect(browser: IBrowser, rect: ClickRect | BBox, highlight?: boolean, highlightDuration?: number, takeSnapshot?: boolean, cursorPolicy?: CursorPolicy): Promise<ActionResult>;
|
|
185
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
|