explorbot 0.5.8 → 0.5.10
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/README.md +1 -1
- package/bin/explorbot-cli.ts +25 -4
- package/boat/api-tester/src/commands/init-command.ts +2 -0
- package/boat/doc-collector/bin/doc-collector-cli.ts +2 -1
- package/boat/prima/README.md +3 -0
- package/boat/prima/bin/prima-cli.ts +2 -1
- package/boat/prima/src/cli.ts +7 -1
- package/boat/prima/src/envelope.ts +18 -4
- package/boat/prima/src/help.ts +15 -4
- package/boat/prima/src/prima.ts +181 -25
- package/dist/bin/explorbot-cli.js +31 -4
- package/dist/boat/api-tester/bin/apibot-cli.js +2 -1
- package/dist/boat/api-tester/src/commands/init-command.js +2 -0
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -1
- package/dist/boat/prima/bin/prima-cli.js +2 -1
- package/dist/boat/prima/src/cli.js +6 -1
- package/dist/boat/prima/src/envelope.js +16 -3
- package/dist/boat/prima/src/help.js +14 -4
- package/dist/boat/prima/src/prima.js +191 -27
- package/dist/package.json +1 -1
- package/dist/rules/navigator/verification-actions.md +5 -3
- package/dist/rules/planner/ignored-pages.md +6 -0
- package/dist/rules/quartermaster/ux/accessibility.md +8 -0
- package/dist/rules/quartermaster/ux/charts.md +6 -0
- package/dist/rules/quartermaster/ux/copy.md +9 -0
- package/dist/rules/quartermaster/ux/forms.md +7 -0
- package/dist/rules/quartermaster/ux/interaction.md +7 -0
- package/dist/rules/quartermaster/ux/motion.md +5 -0
- package/dist/rules/quartermaster/ux/navigation.md +7 -0
- package/dist/rules/quartermaster/ux/readability.md +6 -0
- package/dist/rules/quartermaster/ux/responsive.md +7 -0
- package/dist/src/ai/judge.d.ts +3 -1
- package/dist/src/ai/judge.js +13 -3
- package/dist/src/ai/navigator.js +3 -2
- package/dist/src/ai/pilot.js +10 -4
- package/dist/src/ai/planner.js +9 -1
- package/dist/src/ai/provider.d.ts +4 -0
- package/dist/src/ai/provider.js +27 -5
- package/dist/src/ai/quartermaster.d.ts +31 -3
- package/dist/src/ai/quartermaster.js +262 -17
- package/dist/src/ai/tester.js +6 -3
- package/dist/src/commands/init-command.js +4 -0
- package/dist/src/commands/options/auth-option.d.ts +6 -0
- package/dist/src/commands/options/auth-option.js +14 -0
- package/dist/src/commands/options/index.d.ts +2 -0
- package/dist/src/commands/options/index.js +2 -0
- package/dist/src/config.js +1 -0
- package/dist/src/explorbot.d.ts +1 -1
- package/dist/src/explorbot.js +8 -13
- package/dist/src/explorer.js +1 -0
- package/dist/src/global-config.js +6 -2
- package/dist/src/utils/ux-probe.d.ts +25 -0
- package/dist/src/utils/ux-probe.js +155 -0
- package/docs/reference/commands.md +12 -0
- package/docs/workflow/agentic-usage.md +7 -0
- package/docs/workflow/knowledge.md +6 -0
- package/package.json +1 -1
- package/rules/navigator/verification-actions.md +5 -3
- package/rules/planner/ignored-pages.md +6 -0
- package/rules/quartermaster/ux/accessibility.md +8 -0
- package/rules/quartermaster/ux/charts.md +6 -0
- package/rules/quartermaster/ux/copy.md +9 -0
- package/rules/quartermaster/ux/forms.md +7 -0
- package/rules/quartermaster/ux/interaction.md +7 -0
- package/rules/quartermaster/ux/motion.md +5 -0
- package/rules/quartermaster/ux/navigation.md +7 -0
- package/rules/quartermaster/ux/readability.md +6 -0
- package/rules/quartermaster/ux/responsive.md +7 -0
- package/src/ai/judge.ts +11 -3
- package/src/ai/navigator.ts +2 -2
- package/src/ai/pilot.ts +10 -4
- package/src/ai/planner.ts +10 -1
- package/src/ai/provider.ts +29 -6
- package/src/ai/quartermaster.ts +300 -18
- package/src/ai/tester.ts +5 -3
- package/src/commands/init-command.ts +5 -0
- package/src/commands/options/auth-option.ts +15 -0
- package/src/commands/options/index.ts +2 -0
- package/src/config.ts +1 -0
- package/src/explorbot.ts +9 -13
- package/src/explorer.ts +1 -0
- package/src/global-config.ts +5 -2
- package/src/utils/ux-probe.ts +163 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<navigation priority="HIGH">
|
|
2
|
+
- The current location is marked in the navigation that leads to it.
|
|
3
|
+
- Back behaves predictably; a state worth sharing or returning to has its own URL.
|
|
4
|
+
- Navigation offers few top-level choices; grouping beats a long flat list.
|
|
5
|
+
- Destructive or session-ending items are set apart from ordinary destinations.
|
|
6
|
+
- Modals do not carry primary navigation flows.
|
|
7
|
+
</navigation>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<responsive priority="HIGH">
|
|
2
|
+
- Compare breakpoints: the focus area must stay usable at every width it was measured at.
|
|
3
|
+
- No horizontal scroll; content fits the viewport width. Zoom must never be disabled.
|
|
4
|
+
- When navigation does not fit, it collapses behind one named, reachable control; nothing primary disappears without a way back to it.
|
|
5
|
+
- On narrow screens core content comes first; secondary panels fold, they do not push the task below the fold.
|
|
6
|
+
- Fixed or sticky bars never cover the content or controls beneath them.
|
|
7
|
+
</responsive>
|
package/dist/src/ai/judge.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export declare const JUDGE_PAGE_CAP = 12000;
|
|
|
5
5
|
export declare class Decision {
|
|
6
6
|
readonly value: string | null;
|
|
7
7
|
readonly confidence: number;
|
|
8
|
-
|
|
8
|
+
readonly answered: boolean;
|
|
9
|
+
constructor(value: string | null, confidence: number, answered?: boolean);
|
|
9
10
|
get approved(): boolean;
|
|
10
11
|
get rejected(): boolean;
|
|
11
12
|
}
|
|
@@ -23,6 +24,7 @@ export declare class Judge {
|
|
|
23
24
|
static fromConfig(config: AIConfig['decisionModel']): Judge | null;
|
|
24
25
|
get toolEnabled(): boolean;
|
|
25
26
|
decide(question: string, options: string[] | boolean | null, state: unknown): Promise<Decision>;
|
|
27
|
+
assert(statement: string, state: unknown): Promise<boolean | null>;
|
|
26
28
|
pick(question: string, options: string[], state: unknown): Promise<number | null>;
|
|
27
29
|
consult(question: string, options: string[] | boolean | null, state: unknown): Promise<Decision>;
|
|
28
30
|
request(question: string, options: string[] | boolean | null, state: unknown): Promise<Decision>;
|
package/dist/src/ai/judge.js
CHANGED
|
@@ -10,9 +10,11 @@ export const JUDGE_PAGE_CAP = 12000;
|
|
|
10
10
|
export class Decision {
|
|
11
11
|
value;
|
|
12
12
|
confidence;
|
|
13
|
-
|
|
13
|
+
answered;
|
|
14
|
+
constructor(value, confidence, answered = true) {
|
|
14
15
|
this.value = value;
|
|
15
16
|
this.confidence = confidence;
|
|
17
|
+
this.answered = answered;
|
|
16
18
|
}
|
|
17
19
|
get approved() {
|
|
18
20
|
return this.value !== null;
|
|
@@ -43,9 +45,17 @@ export class Judge {
|
|
|
43
45
|
}
|
|
44
46
|
async decide(question, options, state) {
|
|
45
47
|
if (!this.enabled.direct)
|
|
46
|
-
return new Decision(null, 0);
|
|
48
|
+
return new Decision(null, 0, false);
|
|
47
49
|
return this.consult(question, options, state);
|
|
48
50
|
}
|
|
51
|
+
async assert(statement, state) {
|
|
52
|
+
const decision = await this.decide(statement, null, state);
|
|
53
|
+
if (decision.approved)
|
|
54
|
+
return true;
|
|
55
|
+
if (decision.answered && decision.confidence < 1 - this.threshold)
|
|
56
|
+
return false;
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
49
59
|
async pick(question, options, state) {
|
|
50
60
|
if (new Set(options).size < options.length)
|
|
51
61
|
return null;
|
|
@@ -75,7 +85,7 @@ export class Judge {
|
|
|
75
85
|
instructions = `${question} ${UNDECIDED_HINT}`;
|
|
76
86
|
const answer = await this.provider.decide(state, instructions, list).catch((error) => this.recordFailure(error));
|
|
77
87
|
if (!answer)
|
|
78
|
-
return new Decision(null, 0);
|
|
88
|
+
return new Decision(null, 0, false);
|
|
79
89
|
Observability.getSpan()?.setAttribute('langfuse.observation.output', JSON.stringify(answer));
|
|
80
90
|
if (answer.probability <= this.threshold)
|
|
81
91
|
return new Decision(null, answer.probability);
|
package/dist/src/ai/navigator.js
CHANGED
|
@@ -763,8 +763,9 @@ class Navigator {
|
|
|
763
763
|
page?.setDefaultTimeout(originalTimeout);
|
|
764
764
|
}
|
|
765
765
|
const totalAttempted = Math.min(codeBlocks.length, this.verifyAttempts);
|
|
766
|
-
|
|
767
|
-
|
|
766
|
+
let verified = successfulCodes.length > 0 && !failures;
|
|
767
|
+
if (successfulCodes.length && failures)
|
|
768
|
+
verified = await this.provider.oracle(message, { assertions: results.map(({ code, passed, error }) => ({ code, passed, error })) }, { agentName: 'navigator', telemetry: { functionId: 'navigator.verifyState' } });
|
|
768
769
|
if (alreadyVerified)
|
|
769
770
|
verified = true;
|
|
770
771
|
const inexpressible = !alreadyVerified && totalAttempted === 0;
|
package/dist/src/ai/pilot.js
CHANGED
|
@@ -553,8 +553,10 @@ export class Pilot {
|
|
|
553
553
|
const schema = z.object({
|
|
554
554
|
outcomes: z.array(z.object({
|
|
555
555
|
expectation: z.string().describe('The expected outcome, repeated exactly as it was given'),
|
|
556
|
-
status: z
|
|
557
|
-
|
|
556
|
+
status: z
|
|
557
|
+
.enum(['passed', 'failed', 'unverified', 'contradiction'])
|
|
558
|
+
.describe('passed = the evidence shows it happened to the thing it names, failed = the evidence shows the application did not do it, unverified = the run never established either way, contradiction = the picture and the run disagree'),
|
|
559
|
+
evidence: z.string().nullable().describe('What settled it. For unverified, what stopped the run from checking it. For a contradiction, what each side shows. Null when there is nothing to add'),
|
|
558
560
|
})),
|
|
559
561
|
});
|
|
560
562
|
let pageEvidence = '';
|
|
@@ -593,10 +595,14 @@ export class Pilot {
|
|
|
593
595
|
The log is written in the tester's own words, so an outcome can be satisfied by a step that describes it
|
|
594
596
|
differently. Judge by what the steps show happened, not by whether the wording matches.
|
|
595
597
|
Wording may differ, the subject may not. An outcome refers to what the scenario names or chooses; it holds
|
|
596
|
-
only when that same thing appears. A different route or a different thing reaching a similar state is "failed"
|
|
597
|
-
|
|
598
|
+
only when that same thing appears. A different route or a different thing reaching a similar state is "failed".
|
|
599
|
+
An outcome about something the run never managed to choose or create never passes: it is "failed" when the
|
|
600
|
+
application refused it, "unverified" when the run's own trouble got in the way.
|
|
598
601
|
Choose "unverified" only when the evidence neither shows the outcome happening nor shows it failing —
|
|
599
602
|
that is a statement about the run, not about the application.
|
|
603
|
+
"failed" is a statement about the application. When the run's own trouble — an earlier step it could not
|
|
604
|
+
get past, a control it could not reach or operate — left the outcome untested, it is "unverified", and
|
|
605
|
+
the evidence names that blocker.
|
|
600
606
|
`;
|
|
601
607
|
const settle = (content, model) => this.provider
|
|
602
608
|
.generateObject([{ role: 'user', content }], schema, model, {
|
package/dist/src/ai/planner.js
CHANGED
|
@@ -18,6 +18,7 @@ import { WithSubPages, getPlannedByStateHash, getRegisteredPlan, registerPlan }
|
|
|
18
18
|
import { POSSIBLE_SECTIONS } from "./researcher.js";
|
|
19
19
|
import { findSimilarStateHash } from "./researcher/cache.js";
|
|
20
20
|
import { hasFocusedSection } from "./researcher/focus.js";
|
|
21
|
+
import { RulesLoader } from "../utils/rules-loader.js";
|
|
21
22
|
import { capabilityGroundingRule, dataProtectionRules, fileUploadRule } from "./rules.js";
|
|
22
23
|
const debugLog = createDebug('explorbot:planner');
|
|
23
24
|
const TasksSchema = z.object({
|
|
@@ -33,6 +34,7 @@ const TasksSchema = z.object({
|
|
|
33
34
|
.describe('List of expected outcomes that can be verified. Each outcome should be simple, specific, and easy to check (e.g., "Success message appears", "URL changes to /dashboard", "Form field shows error"). Keep outcomes atomic - do not combine multiple checks into one.'),
|
|
34
35
|
}))
|
|
35
36
|
.describe('List of testing scenarios'),
|
|
37
|
+
notPlannableReason: z.string().nullish().describe('When scenarios is empty, one sentence saying why this page has nothing to test; otherwise null.'),
|
|
36
38
|
});
|
|
37
39
|
const PlannerBase = WithSessionDedup(WithSubPages(Object));
|
|
38
40
|
export class Planner extends PlannerBase {
|
|
@@ -178,6 +180,9 @@ export class Planner extends PlannerBase {
|
|
|
178
180
|
if (!aiResult?.object?.scenarios) {
|
|
179
181
|
throw new Error('No tasks were created successfully');
|
|
180
182
|
}
|
|
183
|
+
if (!this.currentPlan && aiResult.object.scenarios.length === 0 && aiResult.object.notPlannableReason) {
|
|
184
|
+
tag('warning').log(`Page is not plannable: ${aiResult.object.notPlannableReason}`);
|
|
185
|
+
}
|
|
181
186
|
const defaultStartUrl = this.getDefaultStartUrl(state);
|
|
182
187
|
const fromPlanning = aiResult.object.scenarios.map((s) => new Test(s.scenario, s.priority, s.expectedOutcomes, s.startUrl || defaultStartUrl, s.steps || []));
|
|
183
188
|
return { tests: fromPlanning, planName: aiResult.object.planName };
|
|
@@ -303,11 +308,14 @@ export class Planner extends PlannerBase {
|
|
|
303
308
|
conversation.autoTrimTag('tested_scenarios', 10000);
|
|
304
309
|
conversation.autoTrimTag('docs_context', 8000);
|
|
305
310
|
conversation.addUserText(this.getSystemMessage(feature));
|
|
311
|
+
let ignoredPagesRule = '';
|
|
312
|
+
if (!feature)
|
|
313
|
+
ignoredPagesRule = `\n${RulesLoader.loadRules('planner', ['ignored-pages'], state.url)}`;
|
|
306
314
|
const planningPrompt = dedent `
|
|
307
315
|
<task>
|
|
308
316
|
Based on the page research, create ${this.MIN_TASKS}-${this.MAX_TASKS} exploratory testing scenarios.
|
|
309
317
|
For each scenario provide specific steps and expected outcomes.
|
|
310
|
-
Exception: if the page reports the requested resource is missing, shows a failure state, or holds no content and no controls, return an empty scenarios list. Never invent tests for a page with nothing to exercise
|
|
318
|
+
Exception: if the page reports the requested resource is missing, shows a failure state, or holds no content and no controls, return an empty scenarios list. Never invent tests for a page with nothing to exercise.${ignoredPagesRule}
|
|
311
319
|
</task>
|
|
312
320
|
|
|
313
321
|
<rules>
|
|
@@ -3,6 +3,7 @@ import type { ModelMessage } from 'ai';
|
|
|
3
3
|
import { type AIConfig } from '../config.js';
|
|
4
4
|
import { type RetryOptions } from '../utils/retry.js';
|
|
5
5
|
import { Conversation } from './conversation.js';
|
|
6
|
+
import type { Judge } from './judge.js';
|
|
6
7
|
declare class AiError extends Error {
|
|
7
8
|
}
|
|
8
9
|
export declare class ContextLengthError extends Error {
|
|
@@ -10,6 +11,7 @@ export declare class ContextLengthError extends Error {
|
|
|
10
11
|
export declare function flushTelemetry(): Promise<void>;
|
|
11
12
|
export declare class Provider {
|
|
12
13
|
config: AIConfig;
|
|
14
|
+
judge: Judge | null;
|
|
13
15
|
telemetryEnabled: boolean;
|
|
14
16
|
otelSdk: NodeSDK | null;
|
|
15
17
|
defaultRetryOptions: RetryOptions;
|
|
@@ -20,6 +22,8 @@ export declare class Provider {
|
|
|
20
22
|
validateConnection(): Promise<void>;
|
|
21
23
|
stop(): Promise<void>;
|
|
22
24
|
getModelForAgent(agentName?: string): any;
|
|
25
|
+
setJudge(judge: Judge | null): void;
|
|
26
|
+
oracle(statement: string, state: unknown, options?: any): Promise<boolean>;
|
|
23
27
|
getAgenticModel(agentName?: string): any;
|
|
24
28
|
getVisionModel(): any;
|
|
25
29
|
getConfiguredModels(): Record<string, string>;
|
package/dist/src/ai/provider.js
CHANGED
|
@@ -2,11 +2,11 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
2
2
|
import { OpenTelemetry } from '@ai-sdk/otel';
|
|
3
3
|
import { LangfuseSpanProcessor } from '@langfuse/otel';
|
|
4
4
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
5
|
-
import { APICallError, NoObjectGeneratedError, asSchema, extractJsonMiddleware,
|
|
5
|
+
import { APICallError, NoObjectGeneratedError, NoOutputGeneratedError, Output, asSchema, extractJsonMiddleware, generateText, isStepCount, parsePartialJson, registerTelemetry, tool, wrapLanguageModel } from 'ai';
|
|
6
6
|
import dedent from 'dedent';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { clearActivity, setActivity } from "../activity.js";
|
|
9
|
-
import { configuredModels, modelName as getModelName } from '../config.js';
|
|
9
|
+
import { PROVIDERS, configuredModels, modelName as getModelName, modelProvider } from '../config.js';
|
|
10
10
|
import { executionController } from "../execution-controller.js";
|
|
11
11
|
import { Observability } from "../observability.js";
|
|
12
12
|
import { Stats } from "../stats.js";
|
|
@@ -75,6 +75,7 @@ function combinedAbortSignal(controller) {
|
|
|
75
75
|
}
|
|
76
76
|
export class Provider {
|
|
77
77
|
config;
|
|
78
|
+
judge = null;
|
|
78
79
|
telemetryEnabled = false;
|
|
79
80
|
otelSdk = null;
|
|
80
81
|
defaultRetryOptions = {
|
|
@@ -89,6 +90,7 @@ export class Provider {
|
|
|
89
90
|
error.message.includes('AI request timeout') ||
|
|
90
91
|
error.message.includes('schema') ||
|
|
91
92
|
error.message.includes('No object generated') ||
|
|
93
|
+
error.message.includes('No output generated') ||
|
|
92
94
|
error.message.includes('No response text') ||
|
|
93
95
|
error.message.includes('validate JSON')) &&
|
|
94
96
|
!error.message.includes('output truncated at maxTokens'));
|
|
@@ -112,6 +114,13 @@ export class Provider {
|
|
|
112
114
|
});
|
|
113
115
|
}
|
|
114
116
|
catch (error) {
|
|
117
|
+
const envKey = PROVIDERS[modelProvider(this.config.model)]?.envKey;
|
|
118
|
+
const apiKey = envKey && process.env[envKey];
|
|
119
|
+
if (envKey && !apiKey)
|
|
120
|
+
throw new AiError(`AI connection failed: ${envKey} is not set. ${error.message}`);
|
|
121
|
+
if (apiKey && [401, 403].includes(error.statusCode)) {
|
|
122
|
+
throw new AiError(`AI connection failed: ${error.message}. ${envKey}=${apiKey.slice(0, 5)}**${apiKey.slice(-3)} was rejected, check the key`);
|
|
123
|
+
}
|
|
115
124
|
throw new AiError(`AI connection failed: ${error.message}`);
|
|
116
125
|
}
|
|
117
126
|
}
|
|
@@ -125,6 +134,18 @@ export class Provider {
|
|
|
125
134
|
const agentConfig = this.config.agents?.[agentName];
|
|
126
135
|
return agentConfig?.model || this.config.model;
|
|
127
136
|
}
|
|
137
|
+
setJudge(judge) {
|
|
138
|
+
this.judge = judge;
|
|
139
|
+
}
|
|
140
|
+
async oracle(statement, state, options = {}) {
|
|
141
|
+
const judged = await this.judge?.assert(statement, state);
|
|
142
|
+
if (typeof judged === 'boolean')
|
|
143
|
+
return judged;
|
|
144
|
+
const schema = z.object({ holds: z.boolean().describe('whether the statement is true') });
|
|
145
|
+
const content = `${statement}\n\n${JSON.stringify(state, null, 2)}`;
|
|
146
|
+
const response = await this.generateObject([{ role: 'user', content }], schema, this.getAgenticModel(options.agentName), options).catch(() => null);
|
|
147
|
+
return response?.object?.holds === true;
|
|
148
|
+
}
|
|
128
149
|
getAgenticModel(agentName) {
|
|
129
150
|
if (agentName) {
|
|
130
151
|
const agentConfig = this.config.agents?.[agentName];
|
|
@@ -482,11 +503,12 @@ export class Provider {
|
|
|
482
503
|
const modelName = getModelName(modelToUse);
|
|
483
504
|
setActivity(`🤖 Asking ${modelName} for structured output`, 'ai');
|
|
484
505
|
promptLog(`Using model: ${modelName}`);
|
|
485
|
-
const config = this.buildGenerateConfig({ schema }, { model: modelToUse }, options);
|
|
486
506
|
try {
|
|
507
|
+
const { timeout, ...config } = this.buildGenerateConfig({ output: Output.object({ schema }) }, { model: modelToUse }, options);
|
|
487
508
|
promptLog(messages[messages.length - 1].content);
|
|
488
509
|
const response = await this.withModelRequestSlot(() => withRetry(async () => {
|
|
489
|
-
|
|
510
|
+
const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages, ...config, abortSignal: signal }), timeout || 30000));
|
|
511
|
+
return { object: result.output, usage: result.usage };
|
|
490
512
|
}, this.getRetryOptions(options)));
|
|
491
513
|
clearActivity();
|
|
492
514
|
responseLog(response.object);
|
|
@@ -502,7 +524,7 @@ export class Provider {
|
|
|
502
524
|
if (Provider.isContextLengthError(error)) {
|
|
503
525
|
return this.recoverFromContextLength(error, messages, options, (m, o) => this.generateObject(m, schema, model, o));
|
|
504
526
|
}
|
|
505
|
-
if (NoObjectGeneratedError.isInstance(error))
|
|
527
|
+
if (NoObjectGeneratedError.isInstance(error) || NoOutputGeneratedError.isInstance(error))
|
|
506
528
|
return this.recoverWithPlainJson(messages, schema, modelToUse, options);
|
|
507
529
|
throw new AiError(error.message || error.toString());
|
|
508
530
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { Locator, Page } from 'playwright';
|
|
1
2
|
import type { ActionResult } from '../action-result.js';
|
|
2
3
|
import type Explorer from '../explorer.js';
|
|
3
4
|
import type { StateManager, StateTransition, WebPageState } from '../state-manager.js';
|
|
4
5
|
import type { Task } from '../test-plan.js';
|
|
6
|
+
import { type UxMeasurements } from '../utils/ux-probe.js';
|
|
5
7
|
import type { Conversation, ToolExecution } from './conversation.js';
|
|
6
8
|
import type { Provider } from './provider.js';
|
|
7
9
|
export declare class Quartermaster {
|
|
@@ -10,20 +12,46 @@ export declare class Quartermaster {
|
|
|
10
12
|
outputDir: string;
|
|
11
13
|
pageAnalyses: Map<string, PageAnalysis>;
|
|
12
14
|
unsubscribe: (() => void) | null;
|
|
13
|
-
explorer: Explorer
|
|
15
|
+
explorer: Explorer;
|
|
14
16
|
pendingAnalyses: Promise<void>[];
|
|
15
|
-
constructor(provider: Provider, options?: {
|
|
17
|
+
constructor(provider: Provider, explorer: Explorer, options?: {
|
|
16
18
|
model?: any;
|
|
17
19
|
});
|
|
18
|
-
start(
|
|
20
|
+
start(stateManager: StateManager): void;
|
|
19
21
|
stop(): void;
|
|
20
22
|
onPageChange(event: StateTransition): void;
|
|
21
23
|
runAxeAnalysis(state: WebPageState): Promise<void>;
|
|
22
24
|
analyzeSession(task: Task, initialState: ActionResult, conversation: Conversation): Promise<AnalysisReport | null>;
|
|
25
|
+
auditUx(result: ActionResult, context: UxAuditContext): Promise<string>;
|
|
23
26
|
generateSemanticAnalysis(axeViolations: AxeViolation[], executions: ToolExecution[], initialState: ActionResult): Promise<SemanticIssue[]>;
|
|
24
27
|
addNotesToTask(task: Task, report: AnalysisReport): void;
|
|
25
28
|
saveReport(stateHash: string, report: AnalysisReport): void;
|
|
26
29
|
formatReportMarkdown(report: AnalysisReport): string;
|
|
30
|
+
axeViolations(page: Page, include?: string): Promise<AxeViolation[]>;
|
|
31
|
+
markUxFocus(result: ActionResult, context: UxAuditContext): Promise<UxFocus>;
|
|
32
|
+
pickUxFocus(context: UxAuditContext, mark: (locator: Locator) => Promise<boolean>): Promise<UxFocus | null>;
|
|
33
|
+
collectUxEvidence(page: Page): Promise<UxEvidence>;
|
|
34
|
+
animationsIgnoringReducedMotion(page: Page): Promise<number>;
|
|
35
|
+
captureUxBreakpoint(page: Page): Promise<UxBreakpoint>;
|
|
36
|
+
writeUxReport(result: ActionResult, focus: UxFocus, evidence: UxEvidence, context: UxAuditContext): Promise<string>;
|
|
37
|
+
}
|
|
38
|
+
export interface UxAuditContext {
|
|
39
|
+
focus?: string;
|
|
40
|
+
commands: string[];
|
|
41
|
+
}
|
|
42
|
+
interface UxFocus {
|
|
43
|
+
area: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
}
|
|
46
|
+
interface UxBreakpoint {
|
|
47
|
+
measurements: UxMeasurements;
|
|
48
|
+
aria: string;
|
|
49
|
+
screenshot?: string;
|
|
50
|
+
}
|
|
51
|
+
interface UxEvidence {
|
|
52
|
+
breakpoints: UxBreakpoint[];
|
|
53
|
+
axe: AxeViolation[];
|
|
54
|
+
notes: string[];
|
|
27
55
|
}
|
|
28
56
|
interface AxeViolation {
|
|
29
57
|
id: string;
|