explorbot 0.3.5 → 0.4.0
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 +18 -13
- package/boat/doc-collector/src/cli.ts +3 -0
- package/boat/doc-collector/src/docbot.ts +3 -1
- package/boat/prima/src/cli.ts +21 -8
- package/boat/prima/src/envelope.ts +35 -9
- package/boat/prima/src/prima.ts +23 -10
- package/dist/bin/explorbot-cli.js +19 -13
- package/dist/boat/doc-collector/src/cli.js +3 -0
- package/dist/boat/doc-collector/src/docbot.js +3 -1
- package/dist/boat/prima/src/cli.js +19 -8
- package/dist/boat/prima/src/envelope.js +24 -6
- package/dist/boat/prima/src/prima.js +23 -11
- package/dist/package.json +2 -2
- package/dist/src/action-result.d.ts +9 -1
- package/dist/src/action-result.js +57 -18
- package/dist/src/action.d.ts +1 -1
- package/dist/src/action.js +87 -12
- package/dist/src/ai/driller.d.ts +0 -1
- package/dist/src/ai/driller.js +8 -20
- package/dist/src/ai/fisherman-tools.d.ts +9 -0
- package/dist/src/ai/fisherman-tools.js +52 -6
- package/dist/src/ai/fisherman.d.ts +4 -2
- package/dist/src/ai/fisherman.js +48 -27
- package/dist/src/ai/historian/codeceptjs.js +1 -1
- package/dist/src/ai/historian/playwright.js +1 -1
- package/dist/src/ai/pilot.d.ts +1 -0
- package/dist/src/ai/pilot.js +13 -1
- package/dist/src/ai/provider.js +20 -3
- package/dist/src/ai/researcher/deep-analysis.js +1 -3
- package/dist/src/ai/researcher.js +3 -3
- package/dist/src/ai/tester.d.ts +3 -0
- package/dist/src/ai/tester.js +40 -3
- package/dist/src/ai/tools.d.ts +1 -0
- package/dist/src/ai/tools.js +13 -6
- package/dist/src/api/request-result.d.ts +2 -0
- package/dist/src/api/request-result.js +8 -2
- package/dist/src/api/request-store.d.ts +3 -2
- package/dist/src/api/request-store.js +66 -14
- package/dist/src/commands/explore-command.d.ts +6 -0
- package/dist/src/commands/explore-command.js +27 -2
- package/dist/src/commands/freesail-command.js +10 -1
- package/dist/src/commands/plans-command.js +6 -6
- package/dist/src/config.js +1 -0
- package/dist/src/experience-tracker.js +5 -0
- package/dist/src/explorbot.d.ts +0 -1
- package/dist/src/explorbot.js +23 -36
- package/dist/src/state-manager.d.ts +5 -1
- package/dist/src/state-manager.js +10 -7
- package/dist/src/test-plan.d.ts +3 -0
- package/dist/src/test-plan.js +27 -0
- package/dist/src/utils/aria.d.ts +1 -1
- package/dist/src/utils/aria.js +6 -42
- package/dist/src/utils/html-diff.d.ts +4 -0
- package/dist/src/utils/html-diff.js +62 -7
- package/dist/src/utils/html.d.ts +5 -15
- package/dist/src/utils/html.js +14 -85
- package/dist/src/utils/overlay.d.ts +56 -11
- package/dist/src/utils/overlay.js +191 -21
- package/dist/src/utils/request-map.d.ts +7 -0
- package/dist/src/utils/request-map.js +16 -0
- package/dist/src/utils/url-matcher.js +4 -2
- package/docs/reference/commands.md +8 -1
- package/docs/reference/websocket.md +1 -0
- package/docs/superpowers/plans/2026-08-29-fisherman-reliability.md +953 -0
- package/docs/superpowers/plans/2026-08-29-region-states.md +1292 -0
- package/docs/superpowers/plans/2026-08-30-fisherman-live-session-auth.md +457 -0
- package/docs/superpowers/specs/2026-08-29-fisherman-reliability-design.md +45 -0
- package/docs/superpowers/specs/2026-08-29-region-states-design.md +262 -0
- package/docs/superpowers/specs/2026-08-29-region-states-fixes-design.md +269 -0
- package/docs/superpowers/specs/2026-08-30-fisherman-live-session-auth-design.md +37 -0
- package/docs/workflow/agentic-usage.md +1 -0
- package/docs/workflow/ci.md +1 -0
- package/package.json +2 -2
- package/src/action-result.ts +61 -22
- package/src/action.ts +87 -14
- package/src/ai/driller.ts +7 -39
- package/src/ai/fisherman-tools.ts +56 -7
- package/src/ai/fisherman.ts +48 -28
- package/src/ai/historian/codeceptjs.ts +1 -1
- package/src/ai/historian/playwright.ts +1 -1
- package/src/ai/pilot.ts +9 -1
- package/src/ai/provider.ts +21 -3
- package/src/ai/researcher/deep-analysis.ts +1 -2
- package/src/ai/researcher.ts +3 -3
- package/src/ai/tester.ts +40 -3
- package/src/ai/tools.ts +17 -9
- package/src/api/request-result.ts +10 -2
- package/src/api/request-store.ts +60 -13
- package/src/commands/explore-command.ts +25 -2
- package/src/commands/freesail-command.ts +7 -1
- package/src/commands/plans-command.ts +6 -6
- package/src/config.ts +1 -0
- package/src/experience-tracker.ts +5 -1
- package/src/explorbot.ts +20 -36
- package/src/state-manager.ts +13 -7
- package/src/test-plan.ts +29 -0
- package/src/utils/aria.ts +7 -44
- package/src/utils/html-diff.ts +62 -7
- package/src/utils/html.ts +14 -91
- package/src/utils/overlay.ts +226 -23
- package/src/utils/request-map.ts +19 -0
- package/src/utils/url-matcher.ts +3 -2
|
@@ -1,43 +1,213 @@
|
|
|
1
1
|
import { detectFocusArea } from './aria.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
overlaySemanticSelector: '[role="dialog"], [role="alertdialog"], [aria-modal="true"], [role="listbox"], [role="menu"], [role="tooltip"]',
|
|
7
|
-
};
|
|
2
|
+
import { pathToXPath } from './html-diff.js';
|
|
3
|
+
import { extractHeadings } from './html.js';
|
|
4
|
+
import { createDebug } from './logger.js';
|
|
5
|
+
const debugLog = createDebug('explorbot:overlay');
|
|
8
6
|
export class Overlay {
|
|
9
7
|
type;
|
|
10
8
|
name;
|
|
9
|
+
root;
|
|
10
|
+
html;
|
|
11
|
+
xpath;
|
|
12
|
+
parent;
|
|
11
13
|
constructor(data = {}) {
|
|
12
14
|
this.type = data.type ?? null;
|
|
13
15
|
this.name = data.name ?? null;
|
|
16
|
+
this.root = data.root ?? null;
|
|
17
|
+
this.html = data.html ?? null;
|
|
18
|
+
this.xpath = data.xpath ?? null;
|
|
19
|
+
this.parent = data.parent ?? null;
|
|
14
20
|
}
|
|
15
21
|
get detected() {
|
|
22
|
+
return this.type !== null && this.type !== 'region';
|
|
23
|
+
}
|
|
24
|
+
get present() {
|
|
16
25
|
return this.type !== null;
|
|
17
26
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
describe() {
|
|
28
|
+
if (!this.present)
|
|
29
|
+
return '';
|
|
30
|
+
let text = `${this.type} "${this.name || 'unnamed'}" opened`;
|
|
31
|
+
if (this.root)
|
|
32
|
+
text += `, scope: ${this.root}`;
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
withGeometry(geometry) {
|
|
36
|
+
return new Overlay({
|
|
37
|
+
type: this.type,
|
|
38
|
+
name: this.name || geometry.name,
|
|
39
|
+
root: geometry.root,
|
|
40
|
+
html: geometry.html,
|
|
41
|
+
xpath: geometry.xpath,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
withParent(parent) {
|
|
45
|
+
return new Overlay({
|
|
46
|
+
type: this.type,
|
|
47
|
+
name: this.name,
|
|
48
|
+
root: this.root,
|
|
49
|
+
html: this.html,
|
|
50
|
+
xpath: this.xpath,
|
|
51
|
+
parent: { type: parent.type, name: parent.name, root: parent.root, xpath: parent.xpath },
|
|
52
|
+
});
|
|
22
53
|
}
|
|
23
54
|
static fromAria(snapshot) {
|
|
24
55
|
return new Overlay(detectFocusArea(snapshot));
|
|
25
56
|
}
|
|
26
57
|
static resolve(data) {
|
|
27
|
-
if (data.overlayHtml)
|
|
28
|
-
return Overlay.fromHtml(data.overlayHtml);
|
|
29
58
|
if (data.overlay)
|
|
30
59
|
return new Overlay(data.overlay);
|
|
31
60
|
return Overlay.fromAria(data.ariaSnapshot ?? null);
|
|
32
61
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
}
|
|
63
|
+
export class OverlayPage {
|
|
64
|
+
page;
|
|
65
|
+
constructor(page) {
|
|
66
|
+
this.page = page;
|
|
67
|
+
}
|
|
68
|
+
async detectRegion(diff) {
|
|
69
|
+
if (!diff.sameUrl && diff.similarity < SOFT_NAVIGATION_SIMILARITY)
|
|
70
|
+
return null;
|
|
71
|
+
for (const subRoot of this.appearedSubRoots(diff)) {
|
|
72
|
+
const probe = await this.probe(subRoot.elementXPath);
|
|
73
|
+
if (probe?.found && probe.onScreen && !probe.centerBelongs) {
|
|
74
|
+
debugLog('Appeared region is hidden or covered, trying the next candidate');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const overlay = this.toOverlay(subRoot, probe, diff.previousHtml);
|
|
78
|
+
if (!overlay.name && !overlay.root) {
|
|
79
|
+
debugLog('Appeared region has no name and no semantic root, ignoring it');
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
debugLog(`Region detected: ${overlay.describe()}`);
|
|
83
|
+
return overlay;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
async isStillOpen(overlay) {
|
|
88
|
+
if (!overlay.xpath)
|
|
89
|
+
return true;
|
|
90
|
+
const probe = await this.probe(overlay.xpath);
|
|
91
|
+
if (!probe)
|
|
92
|
+
return true;
|
|
93
|
+
if (!probe.found)
|
|
94
|
+
return false;
|
|
95
|
+
if (probe.onScreen && !probe.centerBelongs && !probe.coveredByFloating)
|
|
96
|
+
return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
appearedSubRoots(diff) {
|
|
100
|
+
const candidates = [];
|
|
101
|
+
for (const part of diff.parts) {
|
|
102
|
+
const appeared = part.added.find((line) => line.startsWith('ELEMENT:'));
|
|
103
|
+
if (!appeared)
|
|
104
|
+
continue;
|
|
105
|
+
if (part.subtree.length < SUBROOT_MIN_HTML)
|
|
106
|
+
continue;
|
|
107
|
+
if (diff.pageSize > 0 && part.rawSize > REGION_MAX_RATIO * diff.pageSize) {
|
|
108
|
+
debugLog(`Appeared subtree spans ${Math.round((part.rawSize / diff.pageSize) * 100)}% of the page — a new state, not a region`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
candidates.push({
|
|
112
|
+
container: part.container,
|
|
113
|
+
elementXPath: pathToXPath(appeared.slice('ELEMENT:'.length)),
|
|
114
|
+
subtree: part.subtree,
|
|
115
|
+
size: part.subtree.length,
|
|
116
|
+
rawSize: part.rawSize,
|
|
117
|
+
appearedSelector: part.appearedSelector,
|
|
118
|
+
fresh: !!this.freshHeading(part.subtree, diff.previousHtml),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (candidates.length === 0)
|
|
122
|
+
return [];
|
|
123
|
+
const totalRawSize = candidates.reduce((sum, c) => sum + c.rawSize, 0);
|
|
124
|
+
const largest = Math.max(...candidates.map((c) => c.rawSize));
|
|
125
|
+
if (largest < REGION_DOMINANCE * totalRawSize) {
|
|
126
|
+
debugLog('Changes are scattered across the page, no dominant region');
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
candidates.sort((a, b) => Number(b.fresh) - Number(a.fresh) || b.size - a.size);
|
|
130
|
+
return candidates;
|
|
42
131
|
}
|
|
132
|
+
async probe(xpath) {
|
|
133
|
+
if (!this.page)
|
|
134
|
+
return null;
|
|
135
|
+
return this.page
|
|
136
|
+
.evaluate(({ probeSource, config }) => {
|
|
137
|
+
const probe = new Function(`return ${probeSource}`)();
|
|
138
|
+
return probe(config);
|
|
139
|
+
}, { probeSource: inspectRegion.toString(), config: { xpath } })
|
|
140
|
+
.catch((err) => {
|
|
141
|
+
debugLog('Region probe failed:', err.message);
|
|
142
|
+
return null;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
toOverlay(subRoot, probe, previousHtml) {
|
|
146
|
+
let type = 'region';
|
|
147
|
+
if (probe?.centerBelongs && probe.floating)
|
|
148
|
+
type = 'modal';
|
|
149
|
+
let root = null;
|
|
150
|
+
if (subRoot.container !== 'body' && !subRoot.container.startsWith('//'))
|
|
151
|
+
root = subRoot.container;
|
|
152
|
+
if (!root && subRoot.appearedSelector)
|
|
153
|
+
root = subRoot.appearedSelector;
|
|
154
|
+
return new Overlay({ type, name: this.nameFrom(subRoot.subtree, previousHtml), root, html: subRoot.subtree, xpath: subRoot.elementXPath });
|
|
155
|
+
}
|
|
156
|
+
nameFrom(html, previousHtml) {
|
|
157
|
+
const fresh = this.freshHeading(html, previousHtml);
|
|
158
|
+
if (fresh)
|
|
159
|
+
return fresh;
|
|
160
|
+
const candidates = this.headingsOf(html);
|
|
161
|
+
if (candidates.length === 0)
|
|
162
|
+
return null;
|
|
163
|
+
return candidates.join(' ');
|
|
164
|
+
}
|
|
165
|
+
freshHeading(html, previousHtml) {
|
|
166
|
+
const fresh = this.headingsOf(html).filter((heading) => !previousHtml.includes(heading));
|
|
167
|
+
return fresh[0] ?? null;
|
|
168
|
+
}
|
|
169
|
+
headingsOf(html) {
|
|
170
|
+
const headings = extractHeadings(html);
|
|
171
|
+
return [headings.h1, headings.h2, headings.h3, headings.h4].filter(Boolean);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const SUBROOT_MIN_HTML = 5_000;
|
|
175
|
+
const SOFT_NAVIGATION_SIMILARITY = 50;
|
|
176
|
+
const REGION_MAX_RATIO = 0.6;
|
|
177
|
+
const REGION_DOMINANCE = 0.7;
|
|
178
|
+
// Serialized via toString() into page.evaluate — must stay a plain function with no outer-scope references.
|
|
179
|
+
function inspectRegion(config) {
|
|
180
|
+
const probe = { found: false, onScreen: false, floating: false, centerBelongs: false, coveredByFloating: false };
|
|
181
|
+
const isFloating = (start) => {
|
|
182
|
+
let node = start;
|
|
183
|
+
while (node && node !== document.body) {
|
|
184
|
+
const style = window.getComputedStyle(node);
|
|
185
|
+
if (style.position === 'fixed' || style.position === 'absolute' || (Number.parseInt(style.zIndex || '0', 10) || 0) > 0)
|
|
186
|
+
return true;
|
|
187
|
+
node = node.parentElement;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
};
|
|
191
|
+
const result = document.evaluate(config.xpath, document, null, 9, null);
|
|
192
|
+
const node = result.singleNodeValue;
|
|
193
|
+
if (!node || node.nodeType !== 1)
|
|
194
|
+
return probe;
|
|
195
|
+
const element = node;
|
|
196
|
+
const rect = element.getBoundingClientRect();
|
|
197
|
+
if (rect.width === 0 && rect.height === 0)
|
|
198
|
+
return probe;
|
|
199
|
+
probe.found = true;
|
|
200
|
+
probe.floating = isFloating(element);
|
|
201
|
+
const left = Math.max(rect.left, 0);
|
|
202
|
+
const top = Math.max(rect.top, 0);
|
|
203
|
+
const right = Math.min(rect.right, window.innerWidth);
|
|
204
|
+
const bottom = Math.min(rect.bottom, window.innerHeight);
|
|
205
|
+
if (right <= left || bottom <= top)
|
|
206
|
+
return probe;
|
|
207
|
+
probe.onScreen = true;
|
|
208
|
+
const hit = document.elementFromPoint((left + right) / 2, (top + bottom) / 2);
|
|
209
|
+
probe.centerBelongs = !!hit && (hit === element || element.contains(hit));
|
|
210
|
+
if (hit && !probe.centerBelongs)
|
|
211
|
+
probe.coveredByFloating = isFloating(hit);
|
|
212
|
+
return probe;
|
|
43
213
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RequestResult } from '../api/request-result.js';
|
|
2
|
+
export declare class RequestMap {
|
|
3
|
+
requests: Map<string, RequestResult>;
|
|
4
|
+
constructor(requests?: RequestResult[]);
|
|
5
|
+
add(request: RequestResult): void;
|
|
6
|
+
get(id: string | number): RequestResult | undefined;
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class RequestMap {
|
|
2
|
+
requests = new Map();
|
|
3
|
+
constructor(requests = []) {
|
|
4
|
+
for (const request of requests)
|
|
5
|
+
this.add(request);
|
|
6
|
+
}
|
|
7
|
+
add(request) {
|
|
8
|
+
const { id } = request.extractIdAndTitle();
|
|
9
|
+
if (id === undefined)
|
|
10
|
+
return;
|
|
11
|
+
this.requests.set(String(id), request);
|
|
12
|
+
}
|
|
13
|
+
get(id) {
|
|
14
|
+
return this.requests.get(String(id));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -9,6 +9,8 @@ export function isDynamicSegment(segment) {
|
|
|
9
9
|
catch {
|
|
10
10
|
/* config not loaded yet */
|
|
11
11
|
}
|
|
12
|
+
if (/^v\d+$/i.test(segment))
|
|
13
|
+
return false;
|
|
12
14
|
// numeric: /users/123
|
|
13
15
|
if (/^\d+$/.test(segment))
|
|
14
16
|
return true;
|
|
@@ -18,8 +20,8 @@ export function isDynamicSegment(segment) {
|
|
|
18
20
|
// ULID: /items/01ARZ3NDEKTSV4RRFFQ69G5FAV
|
|
19
21
|
if (/^[0-9A-HJKMNP-TV-Z]{26}$/.test(segment))
|
|
20
22
|
return true;
|
|
21
|
-
// hex ID (4+ chars): /suite/70dae98a
|
|
22
|
-
if (/^[a-f0-9]{4,}$/i.test(segment))
|
|
23
|
+
// hex ID (4+ chars): /suite/70dae98a — short letters-only hex reads as a word
|
|
24
|
+
if (/^[a-f0-9]{4,}$/i.test(segment) && (segment.length >= 8 || /\d/.test(segment)))
|
|
23
25
|
return true;
|
|
24
26
|
// hex-prefixed slug (8+ hex before dash): /suite/95ef0c94-mobile
|
|
25
27
|
if (/^[a-f0-9]{8,}-/i.test(segment))
|
|
@@ -106,6 +106,7 @@ EXPLORBOT_AI_PROVIDER=openrouter \
|
|
|
106
106
|
| `EXPLORBOT_KNOWLEDGE_FILE` | Path to a knowledge markdown file |
|
|
107
107
|
| `EXPLORBOT_API_SPEC` | OpenAPI spec path for the API boat |
|
|
108
108
|
| `EXPLORBOT_NO_BANNER` | Suppress the startup banner, for machine-readable output |
|
|
109
|
+
| `EXPLORBOT_MAX_DURATION` | Wall-clock budget in minutes for an explore run; same as --max-duration |
|
|
109
110
|
<!-- END env -->
|
|
110
111
|
|
|
111
112
|
`npx explorbot config` prints the values a run actually uses — models per role, the config file behind them, the output, knowledge and experience directories, and every `EXPLORBOT_*` variable currently set. The boats answer for their own configuration the same way: `npx explorbot api config`, `npx explorbot docs config`, `npx explorbot prima config`. Add `--json` on any of them to get the same values as an object a script can read.
|
|
@@ -234,6 +235,7 @@ The CLI form navigates to `<path>` first. The TUI form always runs on the curren
|
|
|
234
235
|
| Option | Description |
|
|
235
236
|
|---|---|
|
|
236
237
|
| `--max-tests <n>` | Hard cap on tests executed in this run. Sub-page expansion stops once the cap is hit. |
|
|
238
|
+
| `--max-duration <minutes>` | Wall-clock budget for the whole run. New tests, planning, and sub-page expansion stop before the limit; an in-flight test is cut at the hard cutoff so the report, Testomatio run, and teardown always complete. Set it a few minutes below your CI job timeout. |
|
|
237
239
|
| `--focus <feature>` | Narrow planning to a single feature area (e.g. `--focus checkout`). The focus also becomes part of the saved plan filename. |
|
|
238
240
|
| `--configure <spec>` | Reuse a saved plan, mix old + new tests, filter by style/priority, control sub-page behavior. See below. |
|
|
239
241
|
| `--dry-run` | Mark every picked test as `skipped` instead of executing. New-test planning still runs (so you can preview what would be picked) but no AI tester actions and no plan-file writes. |
|
|
@@ -351,6 +353,7 @@ npx explorbot freesail /dashboard --scope /app --max-tests 20
|
|
|
351
353
|
| `--shallow` | Breadth-first: pick the globally least-visited page |
|
|
352
354
|
| `--scope <prefix>` | Restrict navigation to URLs starting with this prefix |
|
|
353
355
|
| `--max-tests <n>` | Maximum number of tests to run |
|
|
356
|
+
| `--max-duration <minutes>` | Wall-clock budget in minutes for the whole run; freesail stops starting new pages before the limit |
|
|
354
357
|
|
|
355
358
|
### research
|
|
356
359
|
|
|
@@ -431,6 +434,8 @@ npx explorbot test 3 --from-plan output/plans/login.md # index first, plan via
|
|
|
431
434
|
| `--grep <pattern>` | Run only tests whose scenario matches the pattern |
|
|
432
435
|
| `--from-plan <file>` | Load this plan file when the first argument is a test index |
|
|
433
436
|
|
|
437
|
+
The plan names the site it runs against: the URL of its `### Prerequisite` section, or the `## Requirements` URL of its first test. With a [global installation](configuration.md#running-from-anywhere-the-global-installation) that is enough to run a plan from any directory without a project config — `npx explorbot test ~/plans/checkout.md` registers the site and stores its output under `~/.explorbot/sites/<host>/`. Naming a saved plan is enough too: `npx explorbot test checkout` looks for `checkout.md` in the current directory, then in the plans directory of every registered site.
|
|
438
|
+
|
|
434
439
|
### drill
|
|
435
440
|
|
|
436
441
|
Drill all components on a page to learn interactions.
|
|
@@ -685,7 +690,9 @@ html: /home/you/.explorbot/sites/app.example.com/output/prima/2026-08-04T10-04-2
|
|
|
685
690
|
|
|
686
691
|
`used:` is code that already executed: for `go` the CodeceptJS step it ran, for `pw` the Playwright expression you passed, which a CodeceptJS test needs wrapped in `I.usePlaywrightTo(...)`. Log lines can precede the envelope, so start parsing at the first `###` line.
|
|
687
692
|
|
|
688
|
-
`### Changes` renders on every action envelope, saying `no change` when the tree is identical — so a successful command proves what it did instead of leaving you to check. `check` and `do` report per step rather than in aggregate: `### Steps` names each step with the code it ran and what proved it, and `page after each step:`
|
|
693
|
+
`### Changes` renders on every action envelope, saying `no change` when the tree is identical — so a successful command proves what it did instead of leaving you to check. `check` and `do` report per step rather than in aggregate: `### Steps` names each step with the code it ran and what proved it, and `page after each step:` names the per-step captures. `check` adds `### Expected outcomes`; `ask`, `research`, and `verify` add `### Answer`, `### Research`, or `### Assertions`; `pw` adds `### Value` when its expression returns one.
|
|
694
|
+
|
|
695
|
+
`### Artifacts` names the files every command leaves on disk: the aria tree and the html always, `screenshot:` and `network:` when a screenshot was taken or requests were captured. The aria tree carries values and checked states, so one `grep` over it settles a question the envelope did not answer, without asking the page again. `prima status <hash>` reprints those paths for an earlier command and lists whatever else was kept under the hash; it only reads recorded files, so it needs no browser and outlives the session.
|
|
689
696
|
|
|
690
697
|
**A failed action is a failure.** Nothing is retried along a different route and no other element is substituted, so `ok: true` means the action you asked for is the one that landed. A failure adds `### Failure` with the error and the compact ARIA of the page, so you can retarget from the envelope itself instead of opening the artifact files.
|
|
691
698
|
|
|
@@ -19,6 +19,7 @@ Every message is JSON with a `type` and a `ts`, plus whatever that type carries.
|
|
|
19
19
|
| `screenshot` | the screenshot file just written |
|
|
20
20
|
| `research` | the researcher's map of a page: markdown and its file |
|
|
21
21
|
| `report` | the analyst's end-of-session report: markdown and its file |
|
|
22
|
+
| `docs` | the doc collector's spec index: markdown and its file |
|
|
22
23
|
| `activity` | what the run is doing this second |
|
|
23
24
|
| `log` | a log line and its level |
|
|
24
25
|
| `ask` | a question for a human, carrying an `askId` |
|