explorbot 0.1.32-beta.1 → 0.2.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/README.md +14 -14
- package/bin/explorbot-cli.ts +4 -3
- package/boat/doc-collector/src/ai/documentarian.ts +22 -27
- package/boat/doc-collector/src/ai/tools.ts +67 -10
- package/boat/doc-collector/src/docbot.ts +28 -7
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +33 -1
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +3 -3
- package/dist/boat/doc-collector/src/ai/documentarian.js +16 -6
- package/dist/boat/doc-collector/src/ai/tools.js +47 -10
- package/dist/boat/doc-collector/src/docbot.js +25 -6
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +29 -0
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/package.json +20 -3
- package/dist/src/action-result.d.ts +8 -10
- package/dist/src/action-result.js +72 -164
- package/dist/src/action.d.ts +3 -10
- package/dist/src/action.js +18 -111
- package/dist/src/ai/captain/web-mode.js +0 -7
- package/dist/src/ai/captain.d.ts +1 -3
- package/dist/src/ai/captain.js +4 -19
- package/dist/src/ai/conversation.d.ts +2 -5
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +0 -4
- package/dist/src/ai/driller.js +3 -12
- package/dist/src/ai/experience-compactor.d.ts +2 -2
- package/dist/src/ai/experience-compactor.js +16 -33
- package/dist/src/ai/fisherman-tools.js +1 -1
- package/dist/src/ai/fisherman.d.ts +0 -1
- package/dist/src/ai/fisherman.js +0 -3
- package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
- package/dist/src/ai/historian/codeceptjs.js +3 -2
- package/dist/src/ai/historian/playwright.d.ts +2 -0
- package/dist/src/ai/historian/playwright.js +3 -2
- package/dist/src/ai/historian/utils.d.ts +0 -1
- package/dist/src/ai/historian/utils.js +0 -1
- package/dist/src/ai/historian.d.ts +5 -4
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +3 -5
- package/dist/src/ai/navigator.js +11 -46
- package/dist/src/ai/pilot.d.ts +1 -3
- package/dist/src/ai/pilot.js +6 -17
- package/dist/src/ai/planner.d.ts +3 -3
- package/dist/src/ai/planner.js +49 -37
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +77 -136
- package/dist/src/ai/quartermaster.d.ts +21 -22
- package/dist/src/ai/quartermaster.js +4 -10
- package/dist/src/ai/rerunner.d.ts +1 -2
- package/dist/src/ai/rerunner.js +14 -22
- package/dist/src/ai/researcher/cache.js +11 -18
- package/dist/src/ai/researcher/deep-analysis.js +3 -6
- package/dist/src/ai/researcher/parser.js +1 -1
- package/dist/src/ai/researcher/research-result.d.ts +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -19
- package/dist/src/ai/researcher/sections.js +5 -1
- package/dist/src/ai/researcher.js +12 -53
- package/dist/src/ai/rules.d.ts +1 -0
- package/dist/src/ai/rules.js +22 -15
- package/dist/src/ai/session-analyst.js +8 -5
- package/dist/src/ai/task-agent.d.ts +1 -1
- package/dist/src/ai/task-agent.js +2 -27
- package/dist/src/ai/tester.d.ts +0 -2
- package/dist/src/ai/tester.js +10 -61
- package/dist/src/ai/tools.d.ts +18 -7
- package/dist/src/ai/tools.js +119 -101
- package/dist/src/commands/compact-command.d.ts +1 -1
- package/dist/src/commands/compact-command.js +6 -6
- package/dist/src/commands/context-command.js +1 -1
- package/dist/src/commands/context-knowledge-command.js +1 -1
- package/dist/src/commands/experience-command.js +1 -1
- package/dist/src/commands/knows-command.js +1 -1
- package/dist/src/commands/learn-command.js +3 -1
- package/dist/src/components/AddKnowledge.d.ts +2 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/App.js +2 -2
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +12 -0
- package/dist/src/experience-tracker.d.ts +11 -6
- package/dist/src/experience-tracker.js +81 -154
- package/dist/src/explorbot.d.ts +4 -5
- package/dist/src/explorbot.js +17 -51
- package/dist/src/explorer.d.ts +4 -15
- package/dist/src/explorer.js +18 -68
- package/dist/src/knowledge-tracker.d.ts +2 -2
- package/dist/src/knowledge-tracker.js +38 -45
- package/dist/src/state-manager.d.ts +6 -31
- package/dist/src/state-manager.js +10 -129
- package/dist/src/utils/aria.d.ts +6 -1
- package/dist/src/utils/aria.js +4 -1
- package/dist/src/utils/cache.d.ts +15 -0
- package/dist/src/utils/cache.js +34 -0
- package/dist/src/utils/context-formatter.d.ts +6 -0
- package/dist/src/utils/context-formatter.js +15 -27
- package/dist/src/utils/html.d.ts +5 -0
- package/dist/src/utils/html.js +71 -0
- package/dist/src/utils/markdown-files.d.ts +10 -0
- package/dist/src/utils/markdown-files.js +21 -0
- package/dist/src/utils/markdown-query.d.ts +6 -0
- package/dist/src/utils/markdown-query.js +14 -1
- package/dist/src/utils/page-readiness.d.ts +1 -0
- package/dist/src/utils/page-readiness.js +1 -1
- package/dist/src/utils/secrets.js +2 -2
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +10 -0
- package/dist/src/utils/web-element.d.ts +2 -1
- package/dist/src/utils/web-element.js +3 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/package.json +20 -3
- package/src/action-result.ts +70 -173
- package/src/action.ts +16 -130
- package/src/ai/captain/web-mode.ts +0 -7
- package/src/ai/captain.ts +5 -17
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +3 -16
- package/src/ai/experience-compactor.ts +16 -35
- package/src/ai/fisherman-tools.ts +1 -1
- package/src/ai/fisherman.ts +0 -4
- package/src/ai/historian/codeceptjs.ts +4 -2
- package/src/ai/historian/playwright.ts +4 -2
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +4 -7
- package/src/ai/navigator.ts +13 -49
- package/src/ai/pilot.ts +6 -16
- package/src/ai/planner.ts +56 -44
- package/src/ai/provider.ts +85 -145
- package/src/ai/quartermaster.ts +34 -41
- package/src/ai/rerunner.ts +17 -21
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/deep-analysis.ts +3 -6
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +5 -1
- package/src/ai/researcher.ts +11 -59
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +3 -30
- package/src/ai/tester.ts +12 -73
- package/src/ai/tools.ts +119 -106
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-command.ts +1 -1
- package/src/commands/context-knowledge-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +3 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +2 -2
- package/src/config.ts +12 -0
- package/src/experience-tracker.ts +88 -151
- package/src/explorbot.ts +17 -51
- package/src/explorer.ts +20 -72
- package/src/knowledge-tracker.ts +42 -49
- package/src/state-manager.ts +15 -156
- package/src/utils/aria.ts +11 -2
- package/src/utils/cache.ts +40 -0
- package/src/utils/context-formatter.ts +12 -15
- package/src/utils/html.ts +79 -0
- package/src/utils/markdown-files.ts +30 -0
- package/src/utils/markdown-query.ts +15 -1
- package/src/utils/page-readiness.ts +1 -1
- package/src/utils/secrets.ts +2 -3
- package/src/utils/strings.ts +12 -0
- package/src/utils/web-element.ts +4 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/dist/boat/api-tester/example/apibot.config.js +0 -30
|
@@ -2,7 +2,7 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ActionResult } from '../../action-result.ts';
|
|
4
4
|
import { ConfigParser } from '../../config.ts';
|
|
5
|
-
import {
|
|
5
|
+
import type { StateManager } from '../../state-manager.ts';
|
|
6
6
|
import { type PlaywrightRecorder, type TraceCall, renderAssertion, renderCall } from '../../playwright-recorder.ts';
|
|
7
7
|
import type { Plan } from '../../test-plan.ts';
|
|
8
8
|
import { tag } from '../../utils/logger.ts';
|
|
@@ -25,6 +25,7 @@ export function WithPlaywright<T extends Constructor>(Base: T) {
|
|
|
25
25
|
return class extends Base {
|
|
26
26
|
declare playwright: { recorder: PlaywrightRecorder; helper: any } | undefined;
|
|
27
27
|
declare savedFiles: Set<string>;
|
|
28
|
+
declare stateManager?: StateManager;
|
|
28
29
|
|
|
29
30
|
async toPlaywrightCode(conversation: Conversation, scenario: string): Promise<string> {
|
|
30
31
|
const toolExecutions = conversation.getToolExecutions();
|
|
@@ -146,7 +147,8 @@ export function WithPlaywright<T extends Constructor>(Base: T) {
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
private getPlaywrightKnowledgeLines(url: string, indent = ' '): string[] {
|
|
149
|
-
const knowledgeTracker =
|
|
150
|
+
const knowledgeTracker = this.stateManager?.getKnowledgeTracker();
|
|
151
|
+
if (!knowledgeTracker) return [];
|
|
150
152
|
const state = new ActionResult({ url });
|
|
151
153
|
const { wait, waitForElement } = knowledgeTracker.getStateParameters(state, ['wait', 'waitForElement']);
|
|
152
154
|
|
package/src/ai/historian.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import type { ExplorbotConfig } from '../config.ts';
|
|
3
|
-
import { ExperienceTracker } from '../experience-tracker.ts';
|
|
3
|
+
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
4
4
|
import type { PlaywrightRecorder } from '../playwright-recorder.ts';
|
|
5
5
|
import type { Reporter } from '../reporter.ts';
|
|
6
6
|
import type { StateManager } from '../state-manager.ts';
|
|
@@ -13,8 +13,6 @@ import { type PlaywrightMethods, WithPlaywright } from './historian/playwright.t
|
|
|
13
13
|
import { type ScreencastMethods, WithScreencast } from './historian/screencast.ts';
|
|
14
14
|
import type { Provider } from './provider.ts';
|
|
15
15
|
|
|
16
|
-
export { isNonReusableCode } from '../utils/step-analyzer.ts';
|
|
17
|
-
|
|
18
16
|
const HistorianBase = WithScreencast(WithPlaywright(WithCodeceptJS(WithExperience(Object as unknown as new (...args: any[]) => object))));
|
|
19
17
|
|
|
20
18
|
export interface Historian extends ExperienceMethods, CodeceptJSMethods, PlaywrightMethods, ScreencastMethods {}
|
|
@@ -28,10 +26,10 @@ export class Historian extends HistorianBase {
|
|
|
28
26
|
declare playwright: { recorder: PlaywrightRecorder; helper: any } | undefined;
|
|
29
27
|
declare savedFiles: Set<string>;
|
|
30
28
|
|
|
31
|
-
constructor(provider: Provider, experienceTracker
|
|
29
|
+
constructor(provider: Provider, experienceTracker: ExperienceTracker, reporter?: Reporter, stateManager?: StateManager, config?: ExplorbotConfig, playwright?: { recorder: PlaywrightRecorder; helper: any }) {
|
|
32
30
|
super();
|
|
33
31
|
this.provider = provider;
|
|
34
|
-
this.experienceTracker = experienceTracker
|
|
32
|
+
this.experienceTracker = experienceTracker;
|
|
35
33
|
this.reporter = reporter;
|
|
36
34
|
this.stateManager = stateManager;
|
|
37
35
|
this.config = config;
|
|
@@ -52,11 +50,10 @@ export class Historian extends HistorianBase {
|
|
|
52
50
|
return this.isPlaywrightFramework() ? this.savePlaywrightPlanToFile(plan) : this.saveCodeceptPlanToFile(plan);
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
rewriteScenarioInFile(filePath: string, healedSteps: Array<{
|
|
53
|
+
rewriteScenarioInFile(filePath: string, healedSteps: Array<{ original: string; healed: string }>): void {
|
|
56
54
|
let content = readFileSync(filePath, 'utf-8');
|
|
57
55
|
|
|
58
56
|
for (const step of healedSteps) {
|
|
59
|
-
if (!content.includes(step.original)) continue;
|
|
60
57
|
content = content.replace(step.original, step.healed);
|
|
61
58
|
}
|
|
62
59
|
|
package/src/ai/navigator.ts
CHANGED
|
@@ -3,10 +3,10 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from '../action-result.js';
|
|
5
5
|
import type Action from '../action.ts';
|
|
6
|
-
import { ExperienceTracker
|
|
6
|
+
import type { ExperienceTracker } from '../experience-tracker.js';
|
|
7
7
|
import Explorer from '../explorer.ts';
|
|
8
|
-
import { KnowledgeTracker } from '../knowledge-tracker.js';
|
|
9
|
-
import {
|
|
8
|
+
import type { KnowledgeTracker } from '../knowledge-tracker.js';
|
|
9
|
+
import { normalizeUrl } from '../state-manager.js';
|
|
10
10
|
import { extractCodeBlocks } from '../utils/code-extractor.js';
|
|
11
11
|
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
12
12
|
import { createDebug, pluralize, tag } from '../utils/logger.js';
|
|
@@ -15,19 +15,17 @@ import { RulesLoader } from '../utils/rules-loader.ts';
|
|
|
15
15
|
import { extractStatePath } from '../utils/url-matcher.js';
|
|
16
16
|
import type { Agent } from './agent.js';
|
|
17
17
|
import type { Conversation } from './conversation.js';
|
|
18
|
-
import { ExperienceCompactor } from './experience-compactor.js';
|
|
19
18
|
import type { Provider } from './provider.js';
|
|
20
19
|
import { Researcher } from './researcher.ts';
|
|
21
20
|
import { actionRule, locatorRule, unexpectedPopupRule } from './rules.js';
|
|
22
21
|
import { isInteractive } from './task-agent.js';
|
|
23
|
-
import {
|
|
22
|
+
import { createLearnExperienceTool } from './tools.ts';
|
|
24
23
|
|
|
25
24
|
const debugLog = createDebug('explorbot:navigator');
|
|
26
25
|
|
|
27
26
|
class Navigator implements Agent {
|
|
28
27
|
emoji = '🧭';
|
|
29
28
|
private provider: Provider;
|
|
30
|
-
private experienceCompactor: ExperienceCompactor;
|
|
31
29
|
private knowledgeTracker: KnowledgeTracker;
|
|
32
30
|
private experienceTracker: ExperienceTracker;
|
|
33
31
|
private hooksRunner: HooksRunner;
|
|
@@ -73,12 +71,11 @@ class Navigator implements Agent {
|
|
|
73
71
|
`;
|
|
74
72
|
private explorer: Explorer;
|
|
75
73
|
|
|
76
|
-
constructor(explorer: Explorer, provider: Provider
|
|
74
|
+
constructor(explorer: Explorer, provider: Provider) {
|
|
77
75
|
this.provider = provider;
|
|
78
76
|
this.explorer = explorer;
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
81
|
-
this.experienceTracker = experienceTracker || new ExperienceTracker();
|
|
77
|
+
this.knowledgeTracker = explorer.getKnowledgeTracker();
|
|
78
|
+
this.experienceTracker = explorer.getStateManager().getExperienceTracker();
|
|
82
79
|
this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
|
|
83
80
|
}
|
|
84
81
|
|
|
@@ -194,19 +191,9 @@ class Navigator implements Agent {
|
|
|
194
191
|
const action = opts?.action ?? this.explorer.createAction();
|
|
195
192
|
const expectedUrl = opts?.expectedUrl;
|
|
196
193
|
|
|
197
|
-
|
|
194
|
+
const knowledge = this.knowledgeTracker.renderRelevantKnowledge(actionResult);
|
|
198
195
|
let experience = '';
|
|
199
196
|
|
|
200
|
-
const relevantKnowledge = this.knowledgeTracker.getRelevantKnowledge(actionResult);
|
|
201
|
-
if (relevantKnowledge.length > 0) {
|
|
202
|
-
const knowledgeContent = relevantKnowledge.map((k) => k.content).join('\n\n');
|
|
203
|
-
knowledge = `
|
|
204
|
-
<hint>
|
|
205
|
-
Here is relevant knowledge for this page:
|
|
206
|
-
${knowledgeContent}
|
|
207
|
-
</hint>`;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
197
|
if (!actionResult.isInsideIframe) {
|
|
211
198
|
const successful = this.experienceTracker.getSuccessfulExperience(actionResult);
|
|
212
199
|
if (successful.length > 0) {
|
|
@@ -397,7 +384,6 @@ class Navigator implements Agent {
|
|
|
397
384
|
if (freshState.getStateHash() !== prevHash) {
|
|
398
385
|
try {
|
|
399
386
|
const diff = await freshState.diff(prevActionResult);
|
|
400
|
-
await diff.calculate();
|
|
401
387
|
ariaChanges = diff.ariaChanged;
|
|
402
388
|
} catch (err) {
|
|
403
389
|
debugLog('Failed to compute pageDiff for failed URL verification:', err);
|
|
@@ -486,20 +472,13 @@ class Navigator implements Agent {
|
|
|
486
472
|
}
|
|
487
473
|
|
|
488
474
|
private buildExperienceTools(): { learnExperience: unknown } | undefined {
|
|
475
|
+
if (!this.experienceTracker) return undefined;
|
|
489
476
|
const stateManager = this.explorer.getStateManager();
|
|
490
477
|
const getState = () => {
|
|
491
478
|
const s = stateManager.getCurrentState();
|
|
492
479
|
return s ? ActionResult.fromState(s) : null;
|
|
493
480
|
};
|
|
494
|
-
|
|
495
|
-
explorer: this.explorer,
|
|
496
|
-
researcher: null as unknown as Researcher,
|
|
497
|
-
navigator: this,
|
|
498
|
-
experienceTracker: this.experienceTracker,
|
|
499
|
-
getState,
|
|
500
|
-
});
|
|
501
|
-
if (!learnExperience) return undefined;
|
|
502
|
-
return { learnExperience };
|
|
481
|
+
return { learnExperience: createLearnExperienceTool({ getExperienceTracker: () => this.experienceTracker, getState }) };
|
|
503
482
|
}
|
|
504
483
|
|
|
505
484
|
async freeSail(opts?: { strategy?: 'deep' | 'shallow'; scope?: string; visitedUrls?: Set<string> }, actionResult?: ActionResult): Promise<{ target: string; reason: string } | null> {
|
|
@@ -641,26 +620,11 @@ class Navigator implements Agent {
|
|
|
641
620
|
return { verified: cachedVerification, successfulCodes: [], assertionSteps: [], totalAttempted: 0 };
|
|
642
621
|
}
|
|
643
622
|
|
|
644
|
-
|
|
623
|
+
const knowledge = this.knowledgeTracker.renderRelevantKnowledge(actionResult);
|
|
645
624
|
let experience = '';
|
|
646
625
|
|
|
647
|
-
const relevantKnowledge = this.knowledgeTracker.getRelevantKnowledge(actionResult);
|
|
648
|
-
if (relevantKnowledge.length > 0) {
|
|
649
|
-
const knowledgeContent = relevantKnowledge.map((k) => k.content).join('\n\n');
|
|
650
|
-
knowledge = `
|
|
651
|
-
<hint>
|
|
652
|
-
Here is relevant knowledge for this page:
|
|
653
|
-
${knowledgeContent}
|
|
654
|
-
</hint>`;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
626
|
if (!actionResult.isInsideIframe) {
|
|
658
|
-
|
|
659
|
-
if (toc.length > 0) {
|
|
660
|
-
const totalSections = toc.reduce((sum, entry) => sum + entry.sections.length, 0);
|
|
661
|
-
tag('operation').log(`Found ${toc.length} experience ${pluralize(toc.length, 'file')} (${totalSections} sections) for: ${actionResult.url}`);
|
|
662
|
-
experience = renderExperienceToc(toc);
|
|
663
|
-
}
|
|
627
|
+
experience = this.experienceTracker.renderExperienceTocFor(actionResult);
|
|
664
628
|
}
|
|
665
629
|
|
|
666
630
|
const priorVerifications = Object.entries(actionResult.verifications ?? {});
|
|
@@ -764,7 +728,7 @@ class Navigator implements Agent {
|
|
|
764
728
|
|
|
765
729
|
await this.explorer.switchToMainFrame();
|
|
766
730
|
|
|
767
|
-
const verified = await action.attempt(codeBlock, message
|
|
731
|
+
const verified = await action.attempt(codeBlock, message);
|
|
768
732
|
|
|
769
733
|
if (verified) {
|
|
770
734
|
tag('success').log('Verification passed');
|
package/src/ai/pilot.ts
CHANGED
|
@@ -3,7 +3,6 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from '../action-result.ts';
|
|
5
5
|
import { ConfigParser } from '../config.ts';
|
|
6
|
-
import { type ExperienceTracker, renderExperienceToc } from '../experience-tracker.ts';
|
|
7
6
|
import type Explorer from '../explorer.ts';
|
|
8
7
|
import { type Test, TestResult } from '../test-plan.ts';
|
|
9
8
|
import { collectInteractiveNodes, detectFocusArea, extractFocusedElement } from '../utils/aria.ts';
|
|
@@ -32,14 +31,12 @@ export class Pilot implements Agent {
|
|
|
32
31
|
private researcher: Researcher;
|
|
33
32
|
private explorer: Explorer;
|
|
34
33
|
private fisherman: Fisherman | null = null;
|
|
35
|
-
private experienceTracker: ExperienceTracker | null;
|
|
36
34
|
|
|
37
|
-
constructor(provider: Provider, agentTools: any, researcher: Researcher, explorer: Explorer
|
|
35
|
+
constructor(provider: Provider, agentTools: any, researcher: Researcher, explorer: Explorer) {
|
|
38
36
|
this.provider = provider;
|
|
39
37
|
this.agentTools = agentTools;
|
|
40
38
|
this.researcher = researcher;
|
|
41
39
|
this.explorer = explorer;
|
|
42
|
-
this.experienceTracker = experienceTracker || null;
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
setFisherman(fisherman: Fisherman): void {
|
|
@@ -111,7 +108,7 @@ export class Pilot implements Agent {
|
|
|
111
108
|
.string()
|
|
112
109
|
.nullable()
|
|
113
110
|
.describe(
|
|
114
|
-
'REQUIRED whenever decision is "pass" — a one-sentence natural-language claim about the current page that, if true, proves the scenario goal (e.g., "New
|
|
111
|
+
'REQUIRED whenever decision is "pass" — a one-sentence natural-language claim about the current page that, if true, proves the scenario goal (e.g., "New item \\"Foo\\" is visible in the items list"). NOT code: do not write I.*, expect(), .then(), grabTitle, or any JavaScript. Navigator translates the claim into CodeceptJS assertions and runs them; passing assertions are saved to the generated test file. Also use when evidence is insufficient before deciding pass/fail. Leave null for "continue", "fail", or "skipped".'
|
|
115
112
|
),
|
|
116
113
|
});
|
|
117
114
|
|
|
@@ -147,7 +144,7 @@ export class Pilot implements Agent {
|
|
|
147
144
|
- Mixed evidence + final state shows success → pass. Mixed + final state unclear → continue with guidance.
|
|
148
145
|
|
|
149
146
|
When deciding "pass", you MUST also set requestVerification to a one-sentence natural-language
|
|
150
|
-
claim about the current page (e.g., "New
|
|
147
|
+
claim about the current page (e.g., "New item Foo is visible in the items list"). NOT
|
|
151
148
|
code — do not write I.*, expect(), .then(), or any JavaScript. Choose the strongest single
|
|
152
149
|
piece of evidence (a unique element/text that exists ONLY because the scenario succeeded).
|
|
153
150
|
Navigator translates the claim into CodeceptJS assertions; without it the generated test has
|
|
@@ -429,10 +426,6 @@ export class Pilot implements Agent {
|
|
|
429
426
|
|
|
430
427
|
FIRST: Decide if precondition() is needed.
|
|
431
428
|
|
|
432
|
-
${capabilityGroundingRule}
|
|
433
|
-
|
|
434
|
-
${dataProtectionRules}
|
|
435
|
-
|
|
436
429
|
Call precondition() WHEN:
|
|
437
430
|
- The scenario edits/deletes/modifies an item, and you want a DISPOSABLE item to act on safely
|
|
438
431
|
- The scenario needs specific data clearly NOT on the current page (e.g., items with specific statuses for filtering)
|
|
@@ -603,12 +596,9 @@ export class Pilot implements Agent {
|
|
|
603
596
|
}
|
|
604
597
|
|
|
605
598
|
private getExperienceToc(): string {
|
|
606
|
-
if (!this.experienceTracker) return '';
|
|
607
599
|
const state = this.explorer.getStateManager().getCurrentState();
|
|
608
600
|
if (!state) return '';
|
|
609
|
-
|
|
610
|
-
const toc = this.experienceTracker.getExperienceTableOfContents(actionResult);
|
|
611
|
-
return renderExperienceToc(toc);
|
|
601
|
+
return this.explorer.getStateManager().getExperienceTracker().renderExperienceTocFor(ActionResult.fromState(state));
|
|
612
602
|
}
|
|
613
603
|
|
|
614
604
|
private pickPlanningTools() {
|
|
@@ -1056,8 +1046,8 @@ export class Pilot implements Agent {
|
|
|
1056
1046
|
|
|
1057
1047
|
${dataProtectionRules}
|
|
1058
1048
|
|
|
1059
|
-
Describe WHAT to create, not what exists. RIGHT: precondition("1
|
|
1060
|
-
precondition("1
|
|
1049
|
+
Describe WHAT to create, not what exists. RIGHT: precondition("1 post"). WRONG:
|
|
1050
|
+
precondition("1 post named Updated Post with existing comments"). Keep descriptions short.
|
|
1061
1051
|
|
|
1062
1052
|
Response format:
|
|
1063
1053
|
PROGRESS: <1 sentence assessment>
|
package/src/ai/planner.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { ActionResult } from '../action-result.ts';
|
|
4
4
|
import { setActivity } from '../activity.ts';
|
|
5
5
|
import { ConfigParser } from '../config.ts';
|
|
6
|
-
import { ExperienceTracker } from '../experience-tracker.ts';
|
|
6
|
+
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
7
7
|
import type Explorer from '../explorer.ts';
|
|
8
8
|
import { Observability } from '../observability.ts';
|
|
9
9
|
import type { StateManager } from '../state-manager.js';
|
|
@@ -18,10 +18,10 @@ import type { Agent } from './agent.js';
|
|
|
18
18
|
import { Conversation } from './conversation.ts';
|
|
19
19
|
import type { Fisherman } from './fisherman.ts';
|
|
20
20
|
import { WithSessionDedup } from './planner/session-dedup.ts';
|
|
21
|
-
import { getActiveStyle
|
|
21
|
+
import { getActiveStyle } from './planner/styles.ts';
|
|
22
22
|
import { WithSubPages, getPlannedByStateHash, getRegisteredPlan, registerPlan } from './planner/subpages.ts';
|
|
23
23
|
import type { Provider } from './provider.js';
|
|
24
|
-
import { POSSIBLE_SECTIONS, Researcher } from './researcher.ts';
|
|
24
|
+
import { POSSIBLE_SECTIONS, type Researcher } from './researcher.ts';
|
|
25
25
|
import { findSimilarStateHash } from './researcher/cache.ts';
|
|
26
26
|
import { hasFocusedSection } from './researcher/focus.ts';
|
|
27
27
|
import { capabilityGroundingRule, dataProtectionRules, fileUploadRule } from './rules.ts';
|
|
@@ -63,13 +63,13 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
63
63
|
researcher: Researcher;
|
|
64
64
|
private fisherman: Fisherman | null = null;
|
|
65
65
|
|
|
66
|
-
constructor(explorer: Explorer, provider: Provider) {
|
|
66
|
+
constructor(explorer: Explorer, provider: Provider, researcher: Researcher) {
|
|
67
67
|
super();
|
|
68
68
|
this.explorer = explorer;
|
|
69
69
|
this.provider = provider;
|
|
70
|
-
this.researcher =
|
|
70
|
+
this.researcher = researcher;
|
|
71
71
|
this.stateManager = explorer.getStateManager();
|
|
72
|
-
this.experienceTracker =
|
|
72
|
+
this.experienceTracker = explorer.getStateManager().getExperienceTracker();
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
setFisherman(fisherman: Fisherman): void {
|
|
@@ -98,7 +98,7 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
98
98
|
You are ISTQB certified senior manual QA planning exploratory testing session of a web application.
|
|
99
99
|
Each test scenario must complete a meaningful user workflow — not just open a UI element and verify it exists.
|
|
100
100
|
Bad: "Open the Help panel" — just opens something.
|
|
101
|
-
Good: "Create a new
|
|
101
|
+
Good: "Create a new post and verify it appears in the list" — completes a business action.
|
|
102
102
|
</role>
|
|
103
103
|
<task>
|
|
104
104
|
List possible testing scenarios for the web page.
|
|
@@ -171,6 +171,9 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
171
171
|
|
|
172
172
|
const tags = ['planner'];
|
|
173
173
|
if (style) tags.push(style);
|
|
174
|
+
|
|
175
|
+
const existingTestScenarios = this.getExistingTestFileScenarios(state.url);
|
|
176
|
+
|
|
174
177
|
const result = await Observability.run(`planner: ${state.url}`, { tags, sessionId: state.url }, async () => {
|
|
175
178
|
const actionResult = ActionResult.fromState(state);
|
|
176
179
|
const conversation = await this.buildConversation(actionResult, style, parentPlan, feature);
|
|
@@ -211,7 +214,6 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
211
214
|
const defaultStartUrl = this.getDefaultStartUrl(state);
|
|
212
215
|
if (parentPlan) this.currentPlan.parentPlan = parentPlan;
|
|
213
216
|
const allPreviousScenarios = this.getPreviousSessionScenarios();
|
|
214
|
-
const existingTestScenarios = this.getExistingTestFileScenarios(state.url);
|
|
215
217
|
for (const s of existingTestScenarios) allPreviousScenarios.add(s);
|
|
216
218
|
for (const t of tests) {
|
|
217
219
|
if (allPreviousScenarios.has(t.scenario.toLowerCase())) continue;
|
|
@@ -266,6 +268,7 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
266
268
|
}
|
|
267
269
|
|
|
268
270
|
private getExistingTestFileScenarios(currentUrl?: string): Set<string> {
|
|
271
|
+
this.lastSuite = null;
|
|
269
272
|
if (!currentUrl) return new Set<string>();
|
|
270
273
|
try {
|
|
271
274
|
this.lastSuite = new Suite(currentUrl);
|
|
@@ -277,29 +280,36 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
277
280
|
}
|
|
278
281
|
|
|
279
282
|
private cleanExperienceFlows(text: string): string | null {
|
|
280
|
-
const seenTitles = new Set<string>();
|
|
281
283
|
let result = text;
|
|
284
|
+
const seenTitles = new Set<string>();
|
|
285
|
+
for (const selector of ['section2', 'section3']) {
|
|
286
|
+
result = mdq(result)
|
|
287
|
+
.query(selector)
|
|
288
|
+
.replaceEach((section) => {
|
|
289
|
+
const heading = section.query('heading').text().trim();
|
|
290
|
+
const withoutHeadings = mdq(section.text()).query('heading').replace('');
|
|
291
|
+
const body = mdq(withoutHeadings).query('hr').replace('').trim();
|
|
292
|
+
if (body && !seenTitles.has(heading)) {
|
|
293
|
+
seenTitles.add(heading);
|
|
294
|
+
return section.text();
|
|
295
|
+
}
|
|
296
|
+
return '';
|
|
297
|
+
});
|
|
298
|
+
}
|
|
282
299
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
.query(
|
|
287
|
-
.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const blockquotes = section.query('blockquote').each();
|
|
298
|
-
if (blockquotes.length <= 10) continue;
|
|
299
|
-
for (const bq of blockquotes.slice(10)) {
|
|
300
|
-
result = result.replace(bq.text(), '');
|
|
301
|
-
}
|
|
302
|
-
result = result.replace(section.text().trim(), `${section.text().trim()}\n> ... and ${blockquotes.length - 10} more discoveries`);
|
|
300
|
+
const trimmedTitles = new Set<string>();
|
|
301
|
+
for (const selector of ['section2', 'section3']) {
|
|
302
|
+
result = mdq(result)
|
|
303
|
+
.query(selector)
|
|
304
|
+
.replaceEach((section) => {
|
|
305
|
+
const heading = section.query('heading').text().trim();
|
|
306
|
+
if (trimmedTitles.has(heading)) return section.text();
|
|
307
|
+
const count = section.query('blockquote').count();
|
|
308
|
+
if (count <= 10) return section.text();
|
|
309
|
+
const kept = mdq(section.text()).query('blockquote[10:]').replace('');
|
|
310
|
+
trimmedTitles.add(heading);
|
|
311
|
+
return `${kept.trimEnd()}\n> ... and ${count - 10} more discoveries\n`;
|
|
312
|
+
});
|
|
303
313
|
}
|
|
304
314
|
|
|
305
315
|
return result.trim() || null;
|
|
@@ -338,6 +348,7 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
338
348
|
If there are subpages (pages with same URL path) plan testing of those subpages as well
|
|
339
349
|
If you plan to test CRUD operations, plan them in correct order: create, read, update.
|
|
340
350
|
Do not invent specific route names, success messages, validation texts, badge counts, or welcome messages unless they are visible in research, visited pages, or prior observed flows.
|
|
351
|
+
When validation placement or wording was not observed, require feedback associated with the invalid input without inventing a specific location or message.
|
|
341
352
|
If exact wording is unknown, describe the expected result generically, for example "an authentication error is shown" or "the user stays on the login page" instead of guessing the literal text.
|
|
342
353
|
If exact redirect destination is unknown, describe the destination by visible page identity, for example "the dashboard page opens" or "the current workspace home page opens" instead of inventing a URL slug.
|
|
343
354
|
Only propose scenarios whose prerequisites are evident from page research, visited pages, or API data preparation context.
|
|
@@ -378,16 +389,17 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
378
389
|
deep: true,
|
|
379
390
|
});
|
|
380
391
|
let plannerResearch = mdq(research).query('code').replace('');
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
392
|
+
plannerResearch = mdq(plannerResearch)
|
|
393
|
+
.query('table')
|
|
394
|
+
.replaceEach((table) => {
|
|
395
|
+
const rows = table.toJson();
|
|
396
|
+
if (rows.length === 0 || !rows[0].Element) return table.text();
|
|
397
|
+
const elementWithType = rows.map((r) => ({
|
|
398
|
+
Element: r.Element,
|
|
399
|
+
Type: r.Type || '',
|
|
400
|
+
}));
|
|
401
|
+
return jsonToTable(elementWithType, ['Element', 'Type']);
|
|
402
|
+
});
|
|
391
403
|
|
|
392
404
|
const hasFocusedOverlay = hasFocusedSection(plannerResearch);
|
|
393
405
|
const focusNote = hasFocusedOverlay ? "IMPORTANT: One section is marked as **Focused** — this is the user's current focus area. Concentrate testing on the Focused section FIRST — test all interactions inside it before planning tests for the rest of the page." : '';
|
|
@@ -576,17 +588,17 @@ export class Planner extends PlannerBase implements Agent {
|
|
|
576
588
|
|
|
577
589
|
STEPS - actionable commands:
|
|
578
590
|
- Each step should be a specific action to perform
|
|
579
|
-
- Good: "Click Create
|
|
580
|
-
- Good: "Click Star icon on '
|
|
591
|
+
- Good: "Click Create Post button", "Enter 'My New Post' as post title", "Click Save button"
|
|
592
|
+
- Good: "Click Star icon on 'Quarterly Report' item"
|
|
581
593
|
- Bad: "Open the dropdown menu" (opening is not a goal, it's a means)
|
|
582
594
|
- Bad: "Verify modal appears" (verification belongs in expected outcomes, not steps)
|
|
583
595
|
- Steps should form a complete workflow, not stop at opening a UI element
|
|
584
596
|
- NEVER split a workflow across two tests. One test = one complete action + its verification
|
|
585
597
|
|
|
586
598
|
EXPECTED OUTCOMES - verifiable results:
|
|
587
|
-
- Good: "New
|
|
588
|
-
- Good: "
|
|
589
|
-
- Good: "Success message '
|
|
599
|
+
- Good: "New post 'My New Post' appears in the posts list"
|
|
600
|
+
- Good: "Item appears under Starred filter tab"
|
|
601
|
+
- Good: "Success message 'Post created' is displayed"
|
|
590
602
|
- Good when wording is unknown: "An authentication error is displayed"
|
|
591
603
|
- Good when route is unknown: "The workspace home page is displayed"
|
|
592
604
|
- Bad: "Modal is displayed" (just verifying existence, no business value)
|