explorbot 0.5.0 → 0.5.1
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/bin/explorbot-cli.ts +2 -1
- package/boat/prima/README.md +31 -4
- package/boat/prima/bin/prima-cli.ts +2 -1
- package/dist/bin/explorbot-cli.js +2 -1
- package/dist/boat/prima/bin/prima-cli.js +2 -1
- package/dist/package.json +2 -2
- package/dist/src/ai/provider.js +1 -1
- package/dist/src/ai/rules.js +13 -0
- package/dist/src/commands/options/decision-model-option.d.ts +6 -0
- package/dist/src/commands/options/decision-model-option.js +10 -0
- package/dist/src/commands/options/index.d.ts +2 -0
- package/dist/src/commands/options/index.js +2 -0
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.js +11 -0
- package/docs/basics/providers.md +37 -0
- package/docs/index.json +1 -0
- package/docs/reference/commands.md +1 -0
- package/docs/reference/configuration.md +2 -0
- package/docs/web-testing/decisions.md +108 -0
- package/docs/workflow/agentic-usage.md +1 -0
- package/package.json +2 -2
- package/src/ai/provider.ts +1 -1
- package/src/ai/rules.ts +13 -0
- package/src/commands/options/decision-model-option.ts +11 -0
- package/src/commands/options/index.ts +2 -0
- package/src/config.ts +10 -0
package/bin/explorbot-cli.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { HelpJsonCommand } from '../src/commands/help-json-command.js';
|
|
|
12
12
|
import { RecommendedModelsCommand } from '../src/commands/recommended-models-command.js';
|
|
13
13
|
import { App } from '../src/components/App.js';
|
|
14
14
|
import { StatusPane } from '../src/components/StatusPane.js';
|
|
15
|
-
import { knowledgeOption, wsOption } from '../src/commands/options/index.js';
|
|
15
|
+
import { decisionModelOption, knowledgeOption, wsOption } from '../src/commands/options/index.js';
|
|
16
16
|
import { ConfigParser, EXPLORBOT_ENV_VARS, PROVIDERS } from '../src/config.js';
|
|
17
17
|
import { ExplorBot, type ExplorBotOptions } from '../src/explorbot.js';
|
|
18
18
|
import { remote } from '../src/remote.js';
|
|
@@ -33,6 +33,7 @@ const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version as stri
|
|
|
33
33
|
program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
|
|
34
34
|
wsOption.register(program);
|
|
35
35
|
knowledgeOption.register(program);
|
|
36
|
+
decisionModelOption.register(program);
|
|
36
37
|
|
|
37
38
|
process.on('uncaughtException', async (error) => {
|
|
38
39
|
tag('error').log(`Uncaught exception: ${error instanceof Error ? `${error.message}\n${error.stack}` : String(error)}`);
|
package/boat/prima/README.md
CHANGED
|
@@ -61,22 +61,49 @@ prima-cli browser stop
|
|
|
61
61
|
|
|
62
62
|
Prima needs a model, and takes it from the environment. There is no `init` to run and nothing is written for you.
|
|
63
63
|
|
|
64
|
+
The setup we recommend runs everything through OpenRouter with one key: GPT-OSS 120B served by Groq as the main model, and TypeSafe's Jev as the [decision model](https://github.com/testomatio/explorbot/blob/main/docs/web-testing/decisions.md), which answers narrow yes/no and pick-one questions so the main model is called less often.
|
|
65
|
+
|
|
64
66
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
PRIMA_CLI_AI_MODEL=openrouter/openai/gpt-oss-120b:nitro
|
|
68
|
+
PRIMA_CLI_DECISION_MODEL=openrouter/typesafe/jev-1.13
|
|
69
|
+
OPENROUTER_API_KEY=your-key
|
|
67
70
|
```
|
|
68
71
|
|
|
69
|
-
The provider comes from
|
|
72
|
+
The provider comes from each model name, and `:nitro` asks OpenRouter for its fastest host, which is Groq for this model. Jev is optional: leave `PRIMA_CLI_DECISION_MODEL` unset and prima never calls it, or pass `--decision-model` for a single run.
|
|
73
|
+
|
|
74
|
+
Prima is usually run by a coding agent, so the best home for these variables is the agent's own config, where they reach every command the agent runs.
|
|
75
|
+
|
|
76
|
+
Claude Code, in `~/.claude/settings.json`:
|
|
70
77
|
|
|
71
78
|
```json
|
|
72
79
|
{
|
|
73
80
|
"env": {
|
|
74
|
-
"PRIMA_CLI_AI_MODEL": "openrouter/openai/gpt-oss-120b",
|
|
81
|
+
"PRIMA_CLI_AI_MODEL": "openrouter/openai/gpt-oss-120b:nitro",
|
|
82
|
+
"PRIMA_CLI_DECISION_MODEL": "openrouter/typesafe/jev-1.13",
|
|
75
83
|
"OPENROUTER_API_KEY": "your-key"
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
```
|
|
79
87
|
|
|
88
|
+
Codex, in `~/.codex/config.toml`:
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[shell_environment_policy]
|
|
92
|
+
set = { PRIMA_CLI_AI_MODEL = "openrouter/openai/gpt-oss-120b:nitro", PRIMA_CLI_DECISION_MODEL = "openrouter/typesafe/jev-1.13", OPENROUTER_API_KEY = "your-key" }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`set` is applied after Codex filters the inherited environment, so the key survives even where names containing `KEY` are stripped.
|
|
96
|
+
|
|
97
|
+
To run prima yourself, or from any agent that inherits your shell, export them in `~/.bashrc` or `~/.zshrc`:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
export PRIMA_CLI_AI_MODEL=openrouter/openai/gpt-oss-120b:nitro
|
|
101
|
+
export PRIMA_CLI_DECISION_MODEL=openrouter/typesafe/jev-1.13
|
|
102
|
+
export OPENROUTER_API_KEY=your-key
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Fish uses `set -gx` in `~/.config/fish/config.fish` instead of `export`.
|
|
106
|
+
|
|
80
107
|
Screenshot analysis needs its own model — one is never guessed from the main model. Set `PRIMA_CLI_VISION_MODEL`, or pass `--vision-model` for a single run, as `--model` does for the main one. Without it prima still runs, but settles `check` outcomes from the run log rather than from the page as seen.
|
|
81
108
|
|
|
82
109
|
Every `PRIMA_CLI_*` variable mirrors the `EXPLORBOT_*` one of the same name and wins over it, so prima can be pointed at a different provider, model or URL without disturbing an explorbot setup on the same machine. `PRIMA_CLI_AI_MODEL`, `PRIMA_CLI_URL`, `PRIMA_CLI_VISION_MODEL` and the rest all work this way.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { knowledgeOption } from '../../../src/commands/options/index.ts';
|
|
2
|
+
import { decisionModelOption, knowledgeOption } from '../../../src/commands/options/index.ts';
|
|
3
3
|
import { createPrimaCommands } from '../src/cli.ts';
|
|
4
4
|
|
|
5
5
|
const program = createPrimaCommands('prima');
|
|
6
6
|
knowledgeOption.register(program);
|
|
7
|
+
decisionModelOption.register(program);
|
|
7
8
|
program.parse();
|
|
@@ -12,7 +12,7 @@ import { HelpJsonCommand } from '../src/commands/help-json-command.js';
|
|
|
12
12
|
import { RecommendedModelsCommand } from '../src/commands/recommended-models-command.js';
|
|
13
13
|
import { App } from '../src/components/App.js';
|
|
14
14
|
import { StatusPane } from '../src/components/StatusPane.js';
|
|
15
|
-
import { knowledgeOption, wsOption } from '../src/commands/options/index.js';
|
|
15
|
+
import { decisionModelOption, knowledgeOption, wsOption } from '../src/commands/options/index.js';
|
|
16
16
|
import { ConfigParser, EXPLORBOT_ENV_VARS, PROVIDERS } from '../src/config.js';
|
|
17
17
|
import { ExplorBot } from '../src/explorbot.js';
|
|
18
18
|
import { remote } from '../src/remote.js';
|
|
@@ -30,6 +30,7 @@ const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
|
|
|
30
30
|
program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
|
|
31
31
|
wsOption.register(program);
|
|
32
32
|
knowledgeOption.register(program);
|
|
33
|
+
decisionModelOption.register(program);
|
|
33
34
|
process.on('uncaughtException', async (error) => {
|
|
34
35
|
tag('error').log(`Uncaught exception: ${error instanceof Error ? `${error.message}\n${error.stack}` : String(error)}`);
|
|
35
36
|
await flushTelemetry();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { knowledgeOption } from "../../../src/commands/options/index.js";
|
|
2
|
+
import { decisionModelOption, knowledgeOption } from "../../../src/commands/options/index.js";
|
|
3
3
|
import { createPrimaCommands } from "../src/cli.js";
|
|
4
4
|
const program = createPrimaCommands('prima');
|
|
5
5
|
knowledgeOption.register(program);
|
|
6
|
+
decisionModelOption.register(program);
|
|
6
7
|
program.parse();
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explorbot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "CLI app built with React Ink, CodeceptJS, and Playwright",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"bash-tool": "^1.3.15",
|
|
103
103
|
"chalk": "^5.6.2",
|
|
104
104
|
"cli-highlight": "^2.1.11",
|
|
105
|
-
"codeceptjs": "^4.2.0
|
|
105
|
+
"codeceptjs": "^4.2.0",
|
|
106
106
|
"commander": "^14.0.1",
|
|
107
107
|
"debug": "^4.4.3",
|
|
108
108
|
"dedent": "^1.6.0",
|
package/dist/src/ai/provider.js
CHANGED
|
@@ -615,7 +615,7 @@ export class Provider {
|
|
|
615
615
|
if (!this.config.visionModel) {
|
|
616
616
|
throw new Error(`Vision model not configured. Set ai.visionModel in your config; ${getCliName()} recommended-models lists the providers that serve one`);
|
|
617
617
|
}
|
|
618
|
-
setActivity(`🤖 Processing image with ${this.config.visionModel}`, 'ai');
|
|
618
|
+
setActivity(`🤖 Processing image with ${getModelName(this.config.visionModel)}`, 'ai');
|
|
619
619
|
const messages = [
|
|
620
620
|
{
|
|
621
621
|
role: 'user',
|
package/dist/src/ai/rules.js
CHANGED
|
@@ -480,6 +480,19 @@ export const actionRule = dedent `
|
|
|
480
480
|
|
|
481
481
|
After hovering, use see() or context() to check what appeared.
|
|
482
482
|
|
|
483
|
+
### I.seeInClipboard / I.seeClipboardEquals / I.clearClipboard
|
|
484
|
+
|
|
485
|
+
Check what a copy control put into the system clipboard. Run via form(), not click().
|
|
486
|
+
I.seeInClipboard asserts the clipboard contains the text, I.seeClipboardEquals asserts an exact match,
|
|
487
|
+
I.clearClipboard empties it — call it before copying so a stale value can't pass the check.
|
|
488
|
+
|
|
489
|
+
<example>
|
|
490
|
+
I.clearClipboard();
|
|
491
|
+
I.click({ role: 'button', text: 'Copy link' });
|
|
492
|
+
I.seeInClipboard('/invite/');
|
|
493
|
+
I.seeClipboardEquals('ABC-123');
|
|
494
|
+
</example>
|
|
495
|
+
|
|
483
496
|
[DO NEVER USE OTHER CODECEPTJS COMMANDS THAN PROPOSED HERE]
|
|
484
497
|
[INTERACT ONLY WITH ELEMENTS THAT ARE ON THE PAGE HTML]
|
|
485
498
|
[DO NOT USE WAIT FUNCTIONS]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseOption } from './base-option.js';
|
|
2
|
+
export class DecisionModelOption extends BaseOption {
|
|
3
|
+
flags = '--decision-model <model>';
|
|
4
|
+
description = 'Turn on the decision model for this run, as provider/model-id (e.g. openrouter/typesafe/jev-1.13)';
|
|
5
|
+
apply(options) {
|
|
6
|
+
if (!options.decisionModel)
|
|
7
|
+
return;
|
|
8
|
+
process.env.EXPLORBOT_DECISION_MODEL = options.decisionModel;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { DecisionModelOption } from './decision-model-option.js';
|
|
1
2
|
import { KnowledgeOption } from './knowledge-option.js';
|
|
2
3
|
import { WsOption } from './ws-option.js';
|
|
3
4
|
export { BaseOption } from './base-option.js';
|
|
5
|
+
export declare const decisionModelOption: DecisionModelOption;
|
|
4
6
|
export declare const knowledgeOption: KnowledgeOption;
|
|
5
7
|
export declare const wsOption: WsOption;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { DecisionModelOption } from './decision-model-option.js';
|
|
1
2
|
import { KnowledgeOption } from './knowledge-option.js';
|
|
2
3
|
import { WsOption } from './ws-option.js';
|
|
3
4
|
export { BaseOption } from './base-option.js';
|
|
5
|
+
export const decisionModelOption = new DecisionModelOption();
|
|
4
6
|
export const knowledgeOption = new KnowledgeOption();
|
|
5
7
|
export const wsOption = new WsOption();
|
package/dist/src/config.d.ts
CHANGED
|
@@ -270,6 +270,7 @@ export declare class ConfigParser {
|
|
|
270
270
|
static cleanupAllTestDirectories(): void;
|
|
271
271
|
enterGlobalMode(config: ExplorbotConfig, target: string | null): Promise<ExplorbotConfig>;
|
|
272
272
|
applyEnvSpec(config: ExplorbotConfig): void;
|
|
273
|
+
applyEnvDecisionModel(config: ExplorbotConfig): void;
|
|
273
274
|
buildEnvConfig(baseUrl: string | undefined, outputRoot: string): Promise<ExplorbotConfig>;
|
|
274
275
|
findConfigFile(): string | null;
|
|
275
276
|
loadConfigModule(configPath: string): Promise<any>;
|
package/dist/src/config.js
CHANGED
|
@@ -45,6 +45,7 @@ export const EXPLORBOT_ENV_VARS = [
|
|
|
45
45
|
{ name: 'EXPLORBOT_URL', required: true, description: 'Base URL to test; the API boat reads it as the base endpoint' },
|
|
46
46
|
{ name: 'EXPLORBOT_VISION_MODEL', description: 'Screenshot analysis; overrides the provider recommendation' },
|
|
47
47
|
{ name: 'EXPLORBOT_AGENTIC_MODEL', description: 'Captain and Pilot decisions; overrides the provider recommendation' },
|
|
48
|
+
{ name: 'EXPLORBOT_DECISION_MODEL', description: 'Turns on the decision model, as provider/model-id (openrouter/typesafe/jev-1.13 or typesafe/jev-latest); overrides ai.decisionModel' },
|
|
48
49
|
{ name: 'EXPLORBOT_OUTPUT', description: 'Output root for states, plans, research, and reports. Defaults to the site dir under ~/.explorbot/sites' },
|
|
49
50
|
{ name: 'EXPLORBOT_EPHEMERAL', description: 'Keep no state between runs — output goes to a fresh temp directory instead of the site dir' },
|
|
50
51
|
{ name: 'EXPLORBOT_KNOWLEDGE', description: 'Inline knowledge text, applied to every page' },
|
|
@@ -138,6 +139,7 @@ export class ConfigParser {
|
|
|
138
139
|
this.runtimeTarget = target;
|
|
139
140
|
this.configPath = sourcePath;
|
|
140
141
|
this.applyEnvSpec(this.config);
|
|
142
|
+
this.applyEnvDecisionModel(this.config);
|
|
141
143
|
// Restore original directory after successful config load
|
|
142
144
|
if (options?.path && originalCwd !== process.cwd()) {
|
|
143
145
|
process.chdir(originalCwd);
|
|
@@ -300,6 +302,15 @@ export class ConfigParser {
|
|
|
300
302
|
config.dirs = { knowledge: 'knowledge', experience: 'experience', output: 'output' };
|
|
301
303
|
config.dirs.spec = spec;
|
|
302
304
|
}
|
|
305
|
+
applyEnvDecisionModel(config) {
|
|
306
|
+
const spec = process.env.EXPLORBOT_DECISION_MODEL;
|
|
307
|
+
if (!spec)
|
|
308
|
+
return;
|
|
309
|
+
const separator = spec.indexOf('/');
|
|
310
|
+
if (separator < 1)
|
|
311
|
+
throw new Error('EXPLORBOT_DECISION_MODEL must be written as "provider/model-id", e.g. openrouter/typesafe/jev-1.13');
|
|
312
|
+
config.ai.decisionModel = { ...config.ai.decisionModel, provider: spec.slice(0, separator), model: spec.slice(separator + 1) };
|
|
313
|
+
}
|
|
303
314
|
async buildEnvConfig(baseUrl, outputRoot) {
|
|
304
315
|
const provider = process.env.EXPLORBOT_AI_PROVIDER;
|
|
305
316
|
const modelSpec = process.env.EXPLORBOT_AI_MODEL;
|
package/docs/basics/providers.md
CHANGED
|
@@ -22,6 +22,8 @@ Explorbot uses three roles:
|
|
|
22
22
|
|
|
23
23
|
Pick a fast, cheap model for the first two and a stronger one for the third. When a provider has no recommended model for one of these roles, combine it with another provider for that role.
|
|
24
24
|
|
|
25
|
+
An optional [decision model](#decision-model) can answer narrow yes/no and pick-one questions so the other models are called less often.
|
|
26
|
+
|
|
25
27
|
### OpenRouter
|
|
26
28
|
|
|
27
29
|
Start with OpenRouter. One key reaches [many providers and models](https://openrouter.ai/models).
|
|
@@ -308,6 +310,40 @@ Laguna XS is an agentic coding model — fast, cheap, and reliable at tool calli
|
|
|
308
310
|
|
|
309
311
|
Keep `agenticModel` on another provider. Poolside's endpoint accepts `response_format: json_schema` but does not enforce it, so structured-output calls depend on the model volunteering valid JSON. Laguna XS usually does; Laguna S answers in prose instead, which makes it unusable for the Planner, Pilot, and Captain. Laguna S is also slow enough under page-sized prompts to hit Explorbot's request timeouts, so it is not a substitute for Laguna XS in the `model` role either.
|
|
310
312
|
|
|
313
|
+
## Decision Model
|
|
314
|
+
|
|
315
|
+
A decision model is an optional fourth role. It does not generate text or call tools. It receives the current state and one closed question, and returns an answer with a probability. Explorbot uses it for narrow choices that would otherwise take a call to a larger model or a guess.
|
|
316
|
+
|
|
317
|
+
Explorbot supports TypeSafe's Jev, reached through OpenRouter or through TypeSafe's API directly:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
export default {
|
|
321
|
+
ai: {
|
|
322
|
+
// ...your model, visionModel, agenticModel...
|
|
323
|
+
decisionModel: { provider: 'openrouter', model: 'typesafe/jev-1.13' },
|
|
324
|
+
// or: decisionModel: { provider: 'typesafe', model: 'jev-latest' },
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
| Option | Type | Description |
|
|
330
|
+
|--------|------|-------------|
|
|
331
|
+
| `provider` | `'openrouter' \| 'typesafe'` | Authenticates with `OPENROUTER_API_KEY` or `TYPESAFE_API_KEY` |
|
|
332
|
+
| `model` | `string` | Model ID at that provider |
|
|
333
|
+
| `tool` | `boolean` | Give Tester and Pilot a `judge` tool to ask their own questions. Default: `true` |
|
|
334
|
+
| `direct` | `boolean` | Consult the model at Explorbot's built-in decision points. Default: `true` |
|
|
335
|
+
|
|
336
|
+
Without a config file, or to turn it on for one run, set `EXPLORBOT_DECISION_MODEL` or pass `--decision-model` as `provider/model-id`. It overrides `provider` and `model` in the config and keeps `tool` and `direct`:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
EXPLORBOT_DECISION_MODEL=openrouter/typesafe/jev-1.13 npx explorbot explore
|
|
340
|
+
npx prima-cli go "Settings" --decision-model typesafe/jev-latest
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Leave `decisionModel` unset and Explorbot never calls it. An unknown provider or a missing API key stops Explorbot at startup.
|
|
344
|
+
|
|
345
|
+
Explorbot acts on an answer only when it is more than 70% sure, and otherwise does what it would have done without the decision model. See [Decisions](../web-testing/decisions.md) for where it is consulted, the `judge` tool, and what happens when an answer is rejected.
|
|
346
|
+
|
|
311
347
|
## Multi-Provider Configuration
|
|
312
348
|
|
|
313
349
|
Mix clients the same way you assign `model`, `visionModel`, and `agenticModel`. Each field can use a different provider instance — a fast provider does the token-heavy reading while a stronger one makes the decisions:
|
|
@@ -359,4 +395,5 @@ export OPENAI_API_KEY=your-key-here
|
|
|
359
395
|
export ANTHROPIC_API_KEY=your-key-here
|
|
360
396
|
export GOOGLE_API_KEY=your-key-here
|
|
361
397
|
export MISTRAL_API_KEY=your-key-here
|
|
398
|
+
export TYPESAFE_API_KEY=your-key-here
|
|
362
399
|
```
|
package/docs/index.json
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
{ "title": "Planner", "file": "web-testing/planner.md", "description": "Tune what gets tested" },
|
|
23
23
|
{ "title": "Researcher", "file": "web-testing/researcher.md", "description": "How pages are analyzed, and how to tune it" },
|
|
24
24
|
{ "title": "Page interaction", "file": "web-testing/page-interaction.md", "description": "How agents read and act on a page" },
|
|
25
|
+
{ "title": "Decisions", "file": "web-testing/decisions.md", "description": "The optional decision model: built-in decision points, the judge tool, fallbacks" },
|
|
25
26
|
{ "title": "Automated tests", "file": "web-testing/automated-tests.md", "description": "The runnable test files you keep" },
|
|
26
27
|
{ "title": "Rerun", "file": "web-testing/rerun.md", "description": "Re-run generated tests with AI healing" },
|
|
27
28
|
{ "title": "Agents", "file": "web-testing/agents.md", "description": "What each agent does under the hood" },
|
|
@@ -112,6 +112,7 @@ EXPLORBOT_AI_PROVIDER=openrouter \
|
|
|
112
112
|
| `EXPLORBOT_URL` | Base URL to test; the API boat reads it as the base endpoint |
|
|
113
113
|
| `EXPLORBOT_VISION_MODEL` | Screenshot analysis; overrides the provider recommendation |
|
|
114
114
|
| `EXPLORBOT_AGENTIC_MODEL` | Captain and Pilot decisions; overrides the provider recommendation |
|
|
115
|
+
| `EXPLORBOT_DECISION_MODEL` | Turns on the decision model, as provider/model-id (openrouter/typesafe/jev-1.13 or typesafe/jev-latest); overrides ai.decisionModel |
|
|
115
116
|
| `EXPLORBOT_OUTPUT` | Output root for states, plans, research, and reports. Defaults to the site dir under ~/.explorbot/sites |
|
|
116
117
|
| `EXPLORBOT_EPHEMERAL` | Keep no state between runs — output goes to a fresh temp directory instead of the site dir |
|
|
117
118
|
| `EXPLORBOT_KNOWLEDGE` | Inline knowledge text, applied to every page |
|
|
@@ -537,6 +537,7 @@ export default {
|
|
|
537
537
|
ai: {
|
|
538
538
|
model: groq('openai/gpt-oss-20b'), // Default model instance (required)
|
|
539
539
|
visionModel: groq('meta-llama/llama-4-scout-17b-16e-instruct'), // Model for screenshot analysis; setting it enables vision features
|
|
540
|
+
decisionModel: { provider: 'openrouter', model: 'typesafe/jev-1.13' }, // Optional; see providers docs
|
|
540
541
|
config: {}, // Additional provider config
|
|
541
542
|
langfuse: { // Observability settings
|
|
542
543
|
enabled: true,
|
|
@@ -624,6 +625,7 @@ export default {
|
|
|
624
625
|
|
|
625
626
|
- [API testing](../api-testing/basics.md) — API testing setup and commands
|
|
626
627
|
- [AI providers](../basics/providers.md) — provider setup examples
|
|
628
|
+
- [Decisions](../web-testing/decisions.md) — the optional decision model and its fallbacks
|
|
627
629
|
- [Agents](../web-testing/agents.md) — agent descriptions and workflows
|
|
628
630
|
- [Agent hooks](../web-testing/hooks.md) — custom code before and after an agent runs
|
|
629
631
|
- [Researcher agent](../web-testing/researcher.md) — Researcher configuration and usage
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Decisions
|
|
2
|
+
|
|
3
|
+
A decision model answers one closed question about the current state: a yes/no statement, or a pick from a list of options. It returns the answer together with its probability. Explorbot uses it to settle narrow choices quickly and cheaply that would otherwise need a call to a larger model or a guess.
|
|
4
|
+
|
|
5
|
+
The decision model is optional. Without it Explorbot behaves exactly as described elsewhere in these docs. Setup is covered in [AI providers](../basics/providers.md#decision-model).
|
|
6
|
+
|
|
7
|
+
## Decision rules
|
|
8
|
+
|
|
9
|
+
Every question has one of two shapes:
|
|
10
|
+
|
|
11
|
+
| Shape | Asked as | Answer |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **Statement** | "The page shows that this claim is true." | Probability that the statement holds |
|
|
14
|
+
| **Choice** | "Which listed element does the intent name?" plus numbered options | The chosen option and its probability |
|
|
15
|
+
|
|
16
|
+
A choice always includes an extra *undecided* option, so the model can say that none of the options fits.
|
|
17
|
+
|
|
18
|
+
Explorbot acts on an answer only when it is **approved**:
|
|
19
|
+
|
|
20
|
+
- a statement's probability is above **70%**, or
|
|
21
|
+
- a choice picked a real option, not *undecided*, with a probability above **70%**.
|
|
22
|
+
|
|
23
|
+
Anything else counts as **rejected**, including:
|
|
24
|
+
|
|
25
|
+
- a confident "no", or any probability of 70% or lower
|
|
26
|
+
- *undecided*
|
|
27
|
+
- a choice offered with fewer than two options
|
|
28
|
+
- a request timeout (15 seconds) or a failed request
|
|
29
|
+
- `direct: false` in the config, for built-in decision points
|
|
30
|
+
|
|
31
|
+
A rejected answer never means "confidently no". It means Explorbot has no usable answer, so it takes its usual path. Every question is worded so that approving it lets Explorbot take a shortcut and rejecting it keeps the usual path. A wrong or unsure answer can therefore cost a shortcut, but never a check.
|
|
32
|
+
|
|
33
|
+
## Built-in decision points
|
|
34
|
+
|
|
35
|
+
These are fixed places in the code where Explorbot asks the decision model before doing the usual, more expensive work. Setting `direct: false` turns them off.
|
|
36
|
+
|
|
37
|
+
### Tester: locator matches several elements
|
|
38
|
+
|
|
39
|
+
When a click or fill fails because the locator matched more than one element, Explorbot lists the matches and asks which one the step meant.
|
|
40
|
+
|
|
41
|
+
- **Approved:** the Tester is told which element to use and how to target it with `elementIndex`.
|
|
42
|
+
- **Fallback:** the Tester receives the numbered list of matches and picks one itself, narrows the locator, or uses a visual click.
|
|
43
|
+
|
|
44
|
+
### Pilot: progress review
|
|
45
|
+
|
|
46
|
+
Every few steps, Pilot reviews the Tester's recent actions with the `agenticModel`. First it asks whether the run is moving toward its goal and can continue unsupervised for now.
|
|
47
|
+
|
|
48
|
+
- **Approved:** the review is skipped and the Tester continues. This saves one `agenticModel` call.
|
|
49
|
+
- **Fallback:** Pilot runs its full review as usual.
|
|
50
|
+
|
|
51
|
+
### Navigator: claim already verified
|
|
52
|
+
|
|
53
|
+
Before Navigator writes assertions for a claim, it asks whether a claim it has already verified on this page means the same thing, even if it is worded differently.
|
|
54
|
+
|
|
55
|
+
- **Approved:** the claim counts as verified and no assertions are generated or run.
|
|
56
|
+
- **Fallback:** Navigator verifies the claim with assertions as usual.
|
|
57
|
+
|
|
58
|
+
The new claim is not added to the page's verified claims. Only a claim backed by assertions that actually ran is recorded as verified.
|
|
59
|
+
|
|
60
|
+
### Navigator: claim no assertion can express
|
|
61
|
+
|
|
62
|
+
Some claims cannot be turned into an assertion. When that happens, Navigator asks whether the page shows the claim is true.
|
|
63
|
+
|
|
64
|
+
- **Approved:** `verify` reports that the page appears to confirm the claim. The result is marked as a judgement, kept apart from assertions that ran, and the Tester is asked to restate the claim so it can be asserted.
|
|
65
|
+
- **Fallback:** `verify` reports that no assertion could express the claim, and suggests restating it or checking it with `see`.
|
|
66
|
+
|
|
67
|
+
In both cases `verify` returns a failure, because nothing ran in the browser.
|
|
68
|
+
|
|
69
|
+
### Prima: `go` to a described page
|
|
70
|
+
|
|
71
|
+
When `prima go` gets a description of a page instead of a URL, it asks which control on the current page leads there.
|
|
72
|
+
|
|
73
|
+
- **Approved:** Prima clicks that control. It then asks a second question: whether the new page shows the target. Only when that is approved too does Prima report success.
|
|
74
|
+
- **Fallback:** if the choice is rejected, or the click fails, or the page doesn't change, or arrival is not confirmed, Prima hands off to the Navigator as usual.
|
|
75
|
+
|
|
76
|
+
### Prima: `check` expected outcomes
|
|
77
|
+
|
|
78
|
+
When `prima check` settles the expected outcomes of a finished run without a screenshot, it asks what the run established about each outcome: that it happened, or that it did not.
|
|
79
|
+
|
|
80
|
+
- **Approved:** that outcome is settled as passed or failed without calling the `agenticModel`.
|
|
81
|
+
- **Fallback:** the remaining outcomes are settled by the `agenticModel` as usual. When a screenshot is available, all outcomes go to the vision model and the decision model is not asked.
|
|
82
|
+
|
|
83
|
+
## The `judge` tool
|
|
84
|
+
|
|
85
|
+
With `tool: true`, Tester and Pilot get a `judge` tool. They use it when a step depends on reading the page rather than running a command. The tool takes:
|
|
86
|
+
|
|
87
|
+
| Argument | Description |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `question` | A statement to confirm, or the question a list of options answers |
|
|
90
|
+
| `options` | Possible answers. Leave it out to confirm a statement |
|
|
91
|
+
| `context` | Anything the page itself doesn't show |
|
|
92
|
+
|
|
93
|
+
The model supplies only the question. The tool gathers the rest of the state itself: the current scenario, the compact ARIA snapshot of the page (up to 12,000 characters), and the last eight test steps.
|
|
94
|
+
|
|
95
|
+
- **Approved:** the tool returns the answer and its confidence.
|
|
96
|
+
- **Rejected:** the tool returns "Not confirmed" and tells the agent to gather more context or try another route. "Not confirmed" means the page doesn't settle the question. It does not mean the statement is false.
|
|
97
|
+
|
|
98
|
+
`tool` and `direct` are independent. For example, `tool: false` keeps the built-in decision points but stops the agents from asking questions of their own.
|
|
99
|
+
|
|
100
|
+
## Where it is not used
|
|
101
|
+
|
|
102
|
+
- **Pilot's verdict on a test.** Whether a scenario passed, failed or was impossible is always decided by the `agenticModel`.
|
|
103
|
+
- **Filtering experience.** One question per stored experience block, on every prompt, would cost more than it saves.
|
|
104
|
+
- **Generation, screenshots and tool calling.** The decision model reads text and answers questions. It does not write, look at images or act on the page.
|
|
105
|
+
|
|
106
|
+
## Tracing
|
|
107
|
+
|
|
108
|
+
Each question is recorded as a `judge.decide` span with the question, the answer and its confidence. A failed request also records the reason. The TUI shows `⚖️ Asking judge...` while a question is in flight. See [Observability](../contributing/observability.md) for setting up tracing.
|
|
@@ -52,6 +52,7 @@ No `init`, no config file, no project directory, no model IDs to look up. These
|
|
|
52
52
|
| `EXPLORBOT_URL` | yes | Base URL to test; the API boat reads it as the base endpoint |
|
|
53
53
|
| `EXPLORBOT_VISION_MODEL` | no | Screenshot analysis; overrides the provider recommendation |
|
|
54
54
|
| `EXPLORBOT_AGENTIC_MODEL` | no | Captain and Pilot decisions; overrides the provider recommendation |
|
|
55
|
+
| `EXPLORBOT_DECISION_MODEL` | no | Turns on the decision model, as provider/model-id (openrouter/typesafe/jev-1.13 or typesafe/jev-latest); overrides ai.decisionModel |
|
|
55
56
|
| `EXPLORBOT_OUTPUT` | no | Output root for states, plans, research, and reports. Defaults to the site dir under ~/.explorbot/sites |
|
|
56
57
|
| `EXPLORBOT_EPHEMERAL` | no | Keep no state between runs — output goes to a fresh temp directory instead of the site dir |
|
|
57
58
|
| `EXPLORBOT_KNOWLEDGE` | no | Inline knowledge text, applied to every page |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explorbot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "CLI app built with React Ink, CodeceptJS, and Playwright",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"bash-tool": "^1.3.15",
|
|
103
103
|
"chalk": "^5.6.2",
|
|
104
104
|
"cli-highlight": "^2.1.11",
|
|
105
|
-
"codeceptjs": "^4.2.0
|
|
105
|
+
"codeceptjs": "^4.2.0",
|
|
106
106
|
"commander": "^14.0.1",
|
|
107
107
|
"debug": "^4.4.3",
|
|
108
108
|
"dedent": "^1.6.0",
|
package/src/ai/provider.ts
CHANGED
|
@@ -683,7 +683,7 @@ export class Provider {
|
|
|
683
683
|
throw new Error(`Vision model not configured. Set ai.visionModel in your config; ${getCliName()} recommended-models lists the providers that serve one`);
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
-
setActivity(`🤖 Processing image with ${this.config.visionModel}`, 'ai');
|
|
686
|
+
setActivity(`🤖 Processing image with ${getModelName(this.config.visionModel)}`, 'ai');
|
|
687
687
|
|
|
688
688
|
const messages: ModelMessage[] = [
|
|
689
689
|
{
|
package/src/ai/rules.ts
CHANGED
|
@@ -497,6 +497,19 @@ export const actionRule = dedent`
|
|
|
497
497
|
|
|
498
498
|
After hovering, use see() or context() to check what appeared.
|
|
499
499
|
|
|
500
|
+
### I.seeInClipboard / I.seeClipboardEquals / I.clearClipboard
|
|
501
|
+
|
|
502
|
+
Check what a copy control put into the system clipboard. Run via form(), not click().
|
|
503
|
+
I.seeInClipboard asserts the clipboard contains the text, I.seeClipboardEquals asserts an exact match,
|
|
504
|
+
I.clearClipboard empties it — call it before copying so a stale value can't pass the check.
|
|
505
|
+
|
|
506
|
+
<example>
|
|
507
|
+
I.clearClipboard();
|
|
508
|
+
I.click({ role: 'button', text: 'Copy link' });
|
|
509
|
+
I.seeInClipboard('/invite/');
|
|
510
|
+
I.seeClipboardEquals('ABC-123');
|
|
511
|
+
</example>
|
|
512
|
+
|
|
500
513
|
[DO NEVER USE OTHER CODECEPTJS COMMANDS THAN PROPOSED HERE]
|
|
501
514
|
[INTERACT ONLY WITH ELEMENTS THAT ARE ON THE PAGE HTML]
|
|
502
515
|
[DO NOT USE WAIT FUNCTIONS]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseOption } from './base-option.js';
|
|
2
|
+
|
|
3
|
+
export class DecisionModelOption extends BaseOption {
|
|
4
|
+
flags = '--decision-model <model>';
|
|
5
|
+
description = 'Turn on the decision model for this run, as provider/model-id (e.g. openrouter/typesafe/jev-1.13)';
|
|
6
|
+
|
|
7
|
+
protected apply(options: Record<string, any>): void {
|
|
8
|
+
if (!options.decisionModel) return;
|
|
9
|
+
process.env.EXPLORBOT_DECISION_MODEL = options.decisionModel;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { DecisionModelOption } from './decision-model-option.js';
|
|
1
2
|
import { KnowledgeOption } from './knowledge-option.js';
|
|
2
3
|
import { WsOption } from './ws-option.js';
|
|
3
4
|
|
|
4
5
|
export { BaseOption } from './base-option.js';
|
|
5
6
|
|
|
7
|
+
export const decisionModelOption = new DecisionModelOption();
|
|
6
8
|
export const knowledgeOption = new KnowledgeOption();
|
|
7
9
|
export const wsOption = new WsOption();
|
package/src/config.ts
CHANGED
|
@@ -278,6 +278,7 @@ export const EXPLORBOT_ENV_VARS: EnvVar[] = [
|
|
|
278
278
|
{ name: 'EXPLORBOT_URL', required: true, description: 'Base URL to test; the API boat reads it as the base endpoint' },
|
|
279
279
|
{ name: 'EXPLORBOT_VISION_MODEL', description: 'Screenshot analysis; overrides the provider recommendation' },
|
|
280
280
|
{ name: 'EXPLORBOT_AGENTIC_MODEL', description: 'Captain and Pilot decisions; overrides the provider recommendation' },
|
|
281
|
+
{ name: 'EXPLORBOT_DECISION_MODEL', description: 'Turns on the decision model, as provider/model-id (openrouter/typesafe/jev-1.13 or typesafe/jev-latest); overrides ai.decisionModel' },
|
|
281
282
|
{ name: 'EXPLORBOT_OUTPUT', description: 'Output root for states, plans, research, and reports. Defaults to the site dir under ~/.explorbot/sites' },
|
|
282
283
|
{ name: 'EXPLORBOT_EPHEMERAL', description: 'Keep no state between runs — output goes to a fresh temp directory instead of the site dir' },
|
|
283
284
|
{ name: 'EXPLORBOT_KNOWLEDGE', description: 'Inline knowledge text, applied to every page' },
|
|
@@ -423,6 +424,7 @@ export class ConfigParser {
|
|
|
423
424
|
this.runtimeTarget = target;
|
|
424
425
|
this.configPath = sourcePath;
|
|
425
426
|
this.applyEnvSpec(this.config);
|
|
427
|
+
this.applyEnvDecisionModel(this.config);
|
|
426
428
|
|
|
427
429
|
// Restore original directory after successful config load
|
|
428
430
|
if (options?.path && originalCwd !== process.cwd()) {
|
|
@@ -600,6 +602,14 @@ export class ConfigParser {
|
|
|
600
602
|
config.dirs.spec = spec;
|
|
601
603
|
}
|
|
602
604
|
|
|
605
|
+
private applyEnvDecisionModel(config: ExplorbotConfig): void {
|
|
606
|
+
const spec = process.env.EXPLORBOT_DECISION_MODEL;
|
|
607
|
+
if (!spec) return;
|
|
608
|
+
const separator = spec.indexOf('/');
|
|
609
|
+
if (separator < 1) throw new Error('EXPLORBOT_DECISION_MODEL must be written as "provider/model-id", e.g. openrouter/typesafe/jev-1.13');
|
|
610
|
+
config.ai.decisionModel = { ...config.ai.decisionModel, provider: spec.slice(0, separator), model: spec.slice(separator + 1) };
|
|
611
|
+
}
|
|
612
|
+
|
|
603
613
|
private async buildEnvConfig(baseUrl: string | undefined, outputRoot: string): Promise<ExplorbotConfig> {
|
|
604
614
|
const provider = process.env.EXPLORBOT_AI_PROVIDER;
|
|
605
615
|
const modelSpec = process.env.EXPLORBOT_AI_MODEL;
|