explorbot 0.1.20 → 0.1.21
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 +54 -25
- package/boat/doc-collector/src/ai/documentarian.ts +259 -84
- package/boat/doc-collector/src/ai/tools.ts +544 -0
- package/boat/doc-collector/src/cli.ts +1 -0
- package/boat/doc-collector/src/config.ts +2 -0
- package/boat/doc-collector/src/docbot.ts +64 -5
- package/boat/doc-collector/src/docs-renderer.ts +56 -2
- package/dist/bin/explorbot-cli.js +30 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +220 -71
- package/dist/boat/doc-collector/src/ai/tools.js +415 -0
- package/dist/boat/doc-collector/src/cli.js +1 -0
- package/dist/boat/doc-collector/src/config.js +1 -0
- package/dist/boat/doc-collector/src/docbot.js +57 -5
- package/dist/boat/doc-collector/src/docs-renderer.js +46 -0
- package/dist/package.json +1 -1
- package/dist/src/ai/navigator.js +82 -6
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/plans-command.js +83 -0
- package/dist/src/commands/test-command.js +14 -8
- package/dist/src/playwright-recorder.js +19 -5
- package/package.json +1 -1
- package/src/ai/navigator.ts +79 -7
- package/src/commands/index.ts +2 -0
- package/src/commands/plans-command.ts +99 -0
- package/src/commands/test-command.ts +15 -8
- package/src/playwright-recorder.ts +19 -5
package/bin/explorbot-cli.ts
CHANGED
|
@@ -305,38 +305,47 @@ addCommonOptions(program.command('plan:load <planfile> [index]').description('Lo
|
|
|
305
305
|
}
|
|
306
306
|
});
|
|
307
307
|
|
|
308
|
-
addCommonOptions(program.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1,3, 1-5, *, all').option('--grep <pattern>', 'Run tests matching pattern')
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
addCommonOptions(program.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1,3, 1-5, *, all').option('--grep <pattern>', 'Run tests matching pattern').option('--from-plan <file>', 'Load plan file when the first argument is a test index')).action(
|
|
309
|
+
async (planfile, index, options) => {
|
|
310
|
+
try {
|
|
311
|
+
const explorBot = new ExplorBot(buildExplorBotOptions(undefined, options));
|
|
312
|
+
await explorBot.start();
|
|
312
313
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
let planfileArg = planfile;
|
|
315
|
+
let indexArg = index;
|
|
316
|
+
if (options.fromPlan) {
|
|
317
|
+
planfileArg = options.fromPlan;
|
|
318
|
+
indexArg = planfile;
|
|
319
|
+
}
|
|
316
320
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
+
const plan = explorBot.loadPlan(planfileArg);
|
|
322
|
+
const pending = plan.getPendingTests();
|
|
323
|
+
log(`Plan loaded: "${plan.title}" (${plan.tests.length} tests, ${pending.length} pending)`);
|
|
321
324
|
|
|
322
|
-
|
|
323
|
-
|
|
325
|
+
const startUrl = plan.url || pending[0]?.startUrl;
|
|
326
|
+
if (!startUrl) {
|
|
327
|
+
throw new Error('No URL found in plan or tests. Cannot determine where to navigate.');
|
|
328
|
+
}
|
|
324
329
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
else if (options.grep) args = options.grep;
|
|
330
|
+
log(`Navigating to ${startUrl}`);
|
|
331
|
+
await explorBot.visit(startUrl);
|
|
328
332
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
333
|
+
let args = '';
|
|
334
|
+
if (indexArg) args = indexArg;
|
|
335
|
+
else if (options.grep) args = options.grep;
|
|
332
336
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
const { TestCommand } = await import('../src/commands/test-command.js');
|
|
338
|
+
const cmd = new TestCommand(explorBot);
|
|
339
|
+
await cmd.execute(args);
|
|
340
|
+
|
|
341
|
+
await explorBot.stop();
|
|
342
|
+
await showStatsAndExit(0);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
345
|
+
await showStatsAndExit(1);
|
|
346
|
+
}
|
|
338
347
|
}
|
|
339
|
-
|
|
348
|
+
);
|
|
340
349
|
|
|
341
350
|
program
|
|
342
351
|
.command('runs [file]')
|
|
@@ -358,6 +367,26 @@ program
|
|
|
358
367
|
}
|
|
359
368
|
});
|
|
360
369
|
|
|
370
|
+
program
|
|
371
|
+
.command('plans [plan]')
|
|
372
|
+
.description('List saved plans, or show tests for a specific plan')
|
|
373
|
+
.option('-p, --path <path>', 'Working directory path')
|
|
374
|
+
.option('-c, --config <path>', 'Path to configuration file')
|
|
375
|
+
.action(async (plan, options) => {
|
|
376
|
+
try {
|
|
377
|
+
await ConfigParser.getInstance().loadConfig({
|
|
378
|
+
config: options.config,
|
|
379
|
+
path: options.path || process.cwd(),
|
|
380
|
+
});
|
|
381
|
+
const explorBot = new ExplorBot({ path: options.path });
|
|
382
|
+
const { PlansCommand } = await import('../src/commands/plans-command.js');
|
|
383
|
+
await new PlansCommand(explorBot).execute(plan || '');
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
361
390
|
addCommonOptions(program.command('rerun <filename> [index]').description('Re-run generated tests with AI auto-healing')).action(async (filename, index, options) => {
|
|
362
391
|
try {
|
|
363
392
|
const explorBot = new ExplorBot(buildExplorBotOptions(undefined, options));
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { AIProvider } from '../../../../src/ai/provider.ts';
|
|
4
|
+
import type Explorer from '../../../../src/explorer.ts';
|
|
4
5
|
import type { WebPageState } from '../../../../src/state-manager.ts';
|
|
6
|
+
import { tag } from '../../../../src/utils/logger.ts';
|
|
5
7
|
import type { DocbotConfig } from '../config.ts';
|
|
8
|
+
import { collectDocInteractions } from './tools.ts';
|
|
6
9
|
|
|
7
10
|
class Documentarian {
|
|
8
11
|
private provider: AIProvider;
|
|
9
12
|
private config: DocbotConfig;
|
|
13
|
+
private explorer?: Explorer;
|
|
10
14
|
|
|
11
|
-
constructor(provider: AIProvider, config: DocbotConfig = {}) {
|
|
15
|
+
constructor(provider: AIProvider, config: DocbotConfig = {}, explorer?: Explorer) {
|
|
12
16
|
this.provider = provider;
|
|
13
17
|
this.config = config;
|
|
18
|
+
this.explorer = explorer;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
async document(state: WebPageState, research: string): Promise<PageDocumentation> {
|
|
22
|
+
const interactiveEnabled = this.config.docs?.interactive === true && this.explorer;
|
|
23
|
+
if (!interactiveEnabled) {
|
|
24
|
+
tag('info').log('Documentarian: Using static mode (interactive disabled or no explorer)');
|
|
25
|
+
return this.documentStatic(state, research);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
tag('info').log('Documentarian: Using interactive mode with tools');
|
|
29
|
+
return this.documentWithInteraction(state, research);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private async documentStatic(state: WebPageState, research: string): Promise<PageDocumentation> {
|
|
17
33
|
try {
|
|
18
34
|
return await this.generateDocumentation(state, research);
|
|
19
35
|
} catch (error) {
|
|
@@ -25,40 +41,124 @@ class Documentarian {
|
|
|
25
41
|
}
|
|
26
42
|
}
|
|
27
43
|
|
|
44
|
+
private async documentWithInteraction(state: WebPageState, research: string): Promise<PageDocumentation> {
|
|
45
|
+
try {
|
|
46
|
+
tag('info').log('Starting interactive exploration...');
|
|
47
|
+
|
|
48
|
+
const deterministicInteractions = await collectDocInteractions(this.explorer!, state, research);
|
|
49
|
+
const meaningfulInteractions = this.getMeaningfulInteractions(deterministicInteractions);
|
|
50
|
+
if (meaningfulInteractions.length > 0) {
|
|
51
|
+
tag('success').log(`Collected ${meaningfulInteractions.length} deterministic interactions`);
|
|
52
|
+
return await this.generateDocumentationWithInteractions(state, research, meaningfulInteractions);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (deterministicInteractions.length > 0) {
|
|
56
|
+
tag('info').log('Interactive exploration found only low-value navigation changes. Using static documentation.');
|
|
57
|
+
} else {
|
|
58
|
+
tag('info').log('Interactive exploration found no reliable deterministic interactions. Using static documentation.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this.documentStatic(state, research);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
+
tag('warning').log(`Interactive documentation failed: ${message}. Falling back to static.`);
|
|
65
|
+
return this.documentStatic(state, research);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private getMeaningfulInteractions(interactions: StateTransition[]): StateTransition[] {
|
|
70
|
+
return interactions.filter((interaction) => {
|
|
71
|
+
if (interaction.targetUrl) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (interaction.changes?.urlChanged) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if ((interaction.changes?.newElements || 0) > 0) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return (interaction.discoveredUrls || []).length > 0;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async generateDocumentationWithInteractions(state: WebPageState, research: string, interactions: StateTransition[]): Promise<PageDocumentation> {
|
|
85
|
+
const messages = [
|
|
86
|
+
{
|
|
87
|
+
role: 'system' as const,
|
|
88
|
+
content: this.getSystemPrompt(),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
role: 'user' as const,
|
|
92
|
+
content: this.buildPrompt(state, `${research}${this.buildInteractionContext(interactions)}`),
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
|
|
97
|
+
agentName: 'documentarian',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return this.normalizeDocumentation(
|
|
101
|
+
{
|
|
102
|
+
...(response.object as PageDocumentation),
|
|
103
|
+
interactions,
|
|
104
|
+
},
|
|
105
|
+
state,
|
|
106
|
+
research
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private async generateDocumentation(state: WebPageState, research: string, simplified = false): Promise<PageDocumentation> {
|
|
111
|
+
const messages = [
|
|
112
|
+
{
|
|
113
|
+
role: 'system' as const,
|
|
114
|
+
content: this.getSystemPrompt(),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
role: 'user' as const,
|
|
118
|
+
content: this.buildPrompt(state, research, simplified),
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
|
|
123
|
+
agentName: 'documentarian',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return this.normalizeDocumentation(response.object as PageDocumentation, state, research);
|
|
127
|
+
}
|
|
128
|
+
|
|
28
129
|
private getSystemPrompt(): string {
|
|
29
|
-
const customPrompt = this.config.docs?.prompt;
|
|
30
130
|
let promptSuffix = '';
|
|
31
|
-
if (
|
|
32
|
-
promptSuffix =
|
|
131
|
+
if (this.config.docs?.prompt) {
|
|
132
|
+
promptSuffix = this.config.docs.prompt;
|
|
33
133
|
}
|
|
34
134
|
|
|
35
135
|
return dedent`
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
136
|
+
<role>
|
|
137
|
+
You are a product analyst preparing functional website documentation from UI research.
|
|
138
|
+
</role>
|
|
139
|
+
|
|
140
|
+
<task>
|
|
141
|
+
Convert exploratory UI research into a precise spec of what users can do on the current page.
|
|
142
|
+
Distinguish proven capabilities from assumptions.
|
|
143
|
+
Prefer accuracy over coverage.
|
|
144
|
+
</task>
|
|
145
|
+
|
|
146
|
+
<rules>
|
|
147
|
+
Only list capabilities that are grounded in the provided page research.
|
|
148
|
+
Put actions into "can" only when there is direct evidence in the page context.
|
|
149
|
+
Put actions into "might" only when the UI strongly suggests a capability but proof is incomplete.
|
|
150
|
+
Describe each action from the end-user perspective.
|
|
151
|
+
Be explicit about scope:
|
|
152
|
+
- one item
|
|
153
|
+
- list of items
|
|
154
|
+
- bulk operations
|
|
155
|
+
- all items
|
|
156
|
+
- page-level
|
|
157
|
+
Avoid implementation details, selectors, and QA wording.
|
|
158
|
+
Avoid duplicate actions with different phrasing.
|
|
159
|
+
</rules>
|
|
160
|
+
|
|
161
|
+
${promptSuffix}
|
|
62
162
|
`;
|
|
63
163
|
}
|
|
64
164
|
|
|
@@ -68,80 +168,104 @@ class Documentarian {
|
|
|
68
168
|
.slice(0, 50)
|
|
69
169
|
.map((link) => `- ${link.title}: ${link.url}`)
|
|
70
170
|
.join('\n');
|
|
71
|
-
|
|
72
|
-
|
|
171
|
+
|
|
172
|
+
let simplificationNote = '';
|
|
173
|
+
if (simplified) {
|
|
174
|
+
simplificationNote = dedent`
|
|
73
175
|
<fallback_mode>
|
|
74
176
|
The research text was simplified because the original formatting was noisy.
|
|
75
177
|
Ignore malformed table syntax and rely only on clear, repeated signals.
|
|
76
178
|
Prefer fewer actions over speculative coverage.
|
|
77
179
|
</fallback_mode>
|
|
78
|
-
|
|
79
|
-
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
80
182
|
|
|
81
183
|
return dedent`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
184
|
+
<page>
|
|
185
|
+
URL: ${state.url}
|
|
186
|
+
Title: ${state.title || ''}
|
|
187
|
+
Headings: ${headings}
|
|
188
|
+
</page>
|
|
189
|
+
|
|
190
|
+
<navigation_links>
|
|
191
|
+
${links}
|
|
192
|
+
</navigation_links>
|
|
193
|
+
|
|
194
|
+
<research>
|
|
195
|
+
${research}
|
|
196
|
+
</research>
|
|
197
|
+
|
|
198
|
+
${simplificationNote}
|
|
199
|
+
|
|
200
|
+
<output_requirements>
|
|
201
|
+
Return structured data.
|
|
202
|
+
summary: short page purpose statement.
|
|
203
|
+
can: actions you are 100% sure are available on page.
|
|
204
|
+
might: actions that look possible but are not fully proven.
|
|
205
|
+
For each action provide:
|
|
206
|
+
- action: concise user-facing capability phrased as "user can ..."
|
|
207
|
+
- scope: one of one item, list of items, bulk operations, all items, page-level
|
|
208
|
+
- evidence: short reason based on visible UI or research
|
|
209
|
+
</output_requirements>
|
|
108
210
|
`;
|
|
109
211
|
}
|
|
110
212
|
|
|
111
|
-
private
|
|
112
|
-
const
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
});
|
|
213
|
+
private buildInteractionContext(interactions: StateTransition[]): string {
|
|
214
|
+
const lines = interactions
|
|
215
|
+
.map((interaction) => {
|
|
216
|
+
const parts = [`Action: ${interaction.action}`, `Element: ${this.formatInteractionElement(interaction)}`, `Before: ${interaction.before}`, `After: ${interaction.after}`, `Changes: ${this.formatInteractionChanges(interaction)}`];
|
|
217
|
+
if (interaction.targetUrl) {
|
|
218
|
+
parts.push(`Target URL: ${interaction.targetUrl}`);
|
|
219
|
+
}
|
|
220
|
+
if (interaction.discoveredUrls && interaction.discoveredUrls.length > 0) {
|
|
221
|
+
parts.push(`Discovered URLs: ${interaction.discoveredUrls.join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
return `- ${parts.join('\n ')}`;
|
|
224
|
+
})
|
|
225
|
+
.join('\n');
|
|
226
|
+
return dedent`
|
|
126
227
|
|
|
127
|
-
|
|
228
|
+
<interaction_observations>
|
|
229
|
+
These are raw observations collected after interacting with visible controls. They are not semantic conclusions.
|
|
230
|
+
Classify them yourself as proven user capabilities, possible capabilities, navigation, page-state changes, or noise.
|
|
231
|
+
Do not trust the action label as a capability category; use the before/after state, element metadata, and URL changes as evidence.
|
|
232
|
+
${lines}
|
|
233
|
+
</interaction_observations>
|
|
234
|
+
`;
|
|
128
235
|
}
|
|
129
236
|
|
|
130
237
|
private shouldRetryWithSanitizedResearch(error: unknown): boolean {
|
|
131
238
|
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
-
return message.includes('Failed to generate JSON') || message.includes('failed_generation');
|
|
239
|
+
return message.includes('Failed to generate JSON') || message.includes('Failed to validate JSON') || message.includes('failed_generation') || message.includes('No object generated') || message.includes('response did not match schema');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private normalizeDocumentation(documentation: PageDocumentation, _state: WebPageState, _research: string): PageDocumentation {
|
|
243
|
+
const qualityNotes = this.evaluateDocumentationQuality(documentation);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
...documentation,
|
|
247
|
+
qualityNotes,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private evaluateDocumentationQuality(documentation: PageDocumentation): string[] {
|
|
252
|
+
const notes: string[] = [];
|
|
253
|
+
|
|
254
|
+
if ((documentation.interactions || []).length === 0 && this.config.docs?.interactive) {
|
|
255
|
+
notes.push('Interactive exploration did not produce any reliable page-specific transitions for this page.');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return notes;
|
|
133
259
|
}
|
|
134
260
|
|
|
135
261
|
private sanitizeResearch(research: string): string {
|
|
136
|
-
const lines = research.split('\n');
|
|
137
262
|
const sanitized: string[] = [];
|
|
138
263
|
|
|
139
|
-
for (const line of
|
|
264
|
+
for (const line of research.split('\n')) {
|
|
140
265
|
if (!line.trim()) {
|
|
141
266
|
sanitized.push(line);
|
|
142
267
|
continue;
|
|
143
268
|
}
|
|
144
|
-
|
|
145
269
|
if (!line.includes('|')) {
|
|
146
270
|
sanitized.push(line);
|
|
147
271
|
continue;
|
|
@@ -151,12 +275,10 @@ class Documentarian {
|
|
|
151
275
|
if (pipeCount < 2) {
|
|
152
276
|
continue;
|
|
153
277
|
}
|
|
154
|
-
|
|
155
278
|
if (line.includes('|------')) {
|
|
156
279
|
sanitized.push(line);
|
|
157
280
|
continue;
|
|
158
281
|
}
|
|
159
|
-
|
|
160
282
|
if (line.trim().startsWith('|') && pipeCount >= 4) {
|
|
161
283
|
sanitized.push(line);
|
|
162
284
|
}
|
|
@@ -164,6 +286,29 @@ class Documentarian {
|
|
|
164
286
|
|
|
165
287
|
return sanitized.join('\n');
|
|
166
288
|
}
|
|
289
|
+
|
|
290
|
+
private formatInteractionElement(interaction: StateTransition): string {
|
|
291
|
+
if (!interaction.element) {
|
|
292
|
+
return 'unknown element';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const parts = [`role=${interaction.element.role}`, `name=${interaction.element.name}`, `section=${interaction.element.section}`];
|
|
296
|
+
if (interaction.element.container) {
|
|
297
|
+
parts.push(`container=${interaction.element.container}`);
|
|
298
|
+
}
|
|
299
|
+
if (interaction.element.locator) {
|
|
300
|
+
parts.push(`locator=${interaction.element.locator}`);
|
|
301
|
+
}
|
|
302
|
+
return parts.join(', ');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private formatInteractionChanges(interaction: StateTransition): string {
|
|
306
|
+
if (!interaction.changes) {
|
|
307
|
+
return 'unknown changes';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return `urlChanged=${interaction.changes.urlChanged}, newElements=${interaction.changes.newElements}, removedElements=${interaction.changes.removedElements}`;
|
|
311
|
+
}
|
|
167
312
|
}
|
|
168
313
|
|
|
169
314
|
const capabilitySchema = z.object({
|
|
@@ -172,13 +317,43 @@ const capabilitySchema = z.object({
|
|
|
172
317
|
evidence: z.string(),
|
|
173
318
|
});
|
|
174
319
|
|
|
320
|
+
const stateTransitionSchema = z.object({
|
|
321
|
+
action: z.string(),
|
|
322
|
+
before: z.string(),
|
|
323
|
+
after: z.string(),
|
|
324
|
+
targetUrl: z.string().optional(),
|
|
325
|
+
discoveredUrls: z.array(z.string()).optional(),
|
|
326
|
+
newCapabilities: z.array(z.string()).optional(),
|
|
327
|
+
element: z
|
|
328
|
+
.object({
|
|
329
|
+
role: z.string(),
|
|
330
|
+
name: z.string(),
|
|
331
|
+
section: z.string(),
|
|
332
|
+
container: z.string().optional(),
|
|
333
|
+
locator: z.string().optional(),
|
|
334
|
+
})
|
|
335
|
+
.optional(),
|
|
336
|
+
changes: z
|
|
337
|
+
.object({
|
|
338
|
+
urlChanged: z.boolean(),
|
|
339
|
+
newElements: z.number(),
|
|
340
|
+
removedElements: z.number(),
|
|
341
|
+
})
|
|
342
|
+
.optional(),
|
|
343
|
+
});
|
|
344
|
+
|
|
175
345
|
const pageDocumentationSchema = z.object({
|
|
176
346
|
summary: z.string(),
|
|
177
347
|
can: z.array(capabilitySchema),
|
|
178
348
|
might: z.array(capabilitySchema),
|
|
349
|
+
interactions: z.array(stateTransitionSchema).optional(),
|
|
179
350
|
});
|
|
180
351
|
|
|
181
|
-
type
|
|
352
|
+
type StateTransition = z.infer<typeof stateTransitionSchema>;
|
|
353
|
+
type PageDocumentation = z.infer<typeof pageDocumentationSchema> & {
|
|
354
|
+
interactions?: StateTransition[];
|
|
355
|
+
qualityNotes?: string[];
|
|
356
|
+
};
|
|
182
357
|
|
|
183
358
|
export { Documentarian };
|
|
184
|
-
export type { PageDocumentation };
|
|
359
|
+
export type { PageDocumentation, StateTransition };
|