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.
Files changed (168) hide show
  1. package/README.md +14 -14
  2. package/bin/explorbot-cli.ts +4 -3
  3. package/boat/doc-collector/src/ai/documentarian.ts +22 -27
  4. package/boat/doc-collector/src/ai/tools.ts +67 -10
  5. package/boat/doc-collector/src/docbot.ts +28 -7
  6. package/boat/doc-collector/src/docs-renderer.ts +19 -24
  7. package/boat/doc-collector/src/screenshots.ts +33 -1
  8. package/boat/doc-collector/src/state-diagram.ts +281 -0
  9. package/dist/bin/explorbot-cli.js +3 -3
  10. package/dist/boat/doc-collector/src/ai/documentarian.js +16 -6
  11. package/dist/boat/doc-collector/src/ai/tools.js +47 -10
  12. package/dist/boat/doc-collector/src/docbot.js +25 -6
  13. package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
  14. package/dist/boat/doc-collector/src/screenshots.js +29 -0
  15. package/dist/boat/doc-collector/src/state-diagram.js +200 -0
  16. package/dist/package.json +20 -3
  17. package/dist/src/action-result.d.ts +8 -10
  18. package/dist/src/action-result.js +72 -164
  19. package/dist/src/action.d.ts +3 -10
  20. package/dist/src/action.js +18 -111
  21. package/dist/src/ai/captain/web-mode.js +0 -7
  22. package/dist/src/ai/captain.d.ts +1 -3
  23. package/dist/src/ai/captain.js +4 -19
  24. package/dist/src/ai/conversation.d.ts +2 -5
  25. package/dist/src/ai/conversation.js +9 -28
  26. package/dist/src/ai/driller.d.ts +0 -4
  27. package/dist/src/ai/driller.js +3 -12
  28. package/dist/src/ai/experience-compactor.d.ts +2 -2
  29. package/dist/src/ai/experience-compactor.js +16 -33
  30. package/dist/src/ai/fisherman-tools.js +1 -1
  31. package/dist/src/ai/fisherman.d.ts +0 -1
  32. package/dist/src/ai/fisherman.js +0 -3
  33. package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
  34. package/dist/src/ai/historian/codeceptjs.js +3 -2
  35. package/dist/src/ai/historian/playwright.d.ts +2 -0
  36. package/dist/src/ai/historian/playwright.js +3 -2
  37. package/dist/src/ai/historian/utils.d.ts +0 -1
  38. package/dist/src/ai/historian/utils.js +0 -1
  39. package/dist/src/ai/historian.d.ts +5 -4
  40. package/dist/src/ai/historian.js +1 -5
  41. package/dist/src/ai/navigator.d.ts +3 -5
  42. package/dist/src/ai/navigator.js +11 -46
  43. package/dist/src/ai/pilot.d.ts +1 -3
  44. package/dist/src/ai/pilot.js +6 -17
  45. package/dist/src/ai/planner.d.ts +3 -3
  46. package/dist/src/ai/planner.js +49 -37
  47. package/dist/src/ai/provider.d.ts +4 -1
  48. package/dist/src/ai/provider.js +77 -136
  49. package/dist/src/ai/quartermaster.d.ts +21 -22
  50. package/dist/src/ai/quartermaster.js +4 -10
  51. package/dist/src/ai/rerunner.d.ts +1 -2
  52. package/dist/src/ai/rerunner.js +14 -22
  53. package/dist/src/ai/researcher/cache.js +11 -18
  54. package/dist/src/ai/researcher/deep-analysis.js +3 -6
  55. package/dist/src/ai/researcher/parser.js +1 -1
  56. package/dist/src/ai/researcher/research-result.d.ts +0 -1
  57. package/dist/src/ai/researcher/research-result.js +0 -19
  58. package/dist/src/ai/researcher/sections.js +5 -1
  59. package/dist/src/ai/researcher.js +12 -53
  60. package/dist/src/ai/rules.d.ts +1 -0
  61. package/dist/src/ai/rules.js +22 -15
  62. package/dist/src/ai/session-analyst.js +8 -5
  63. package/dist/src/ai/task-agent.d.ts +1 -1
  64. package/dist/src/ai/task-agent.js +2 -27
  65. package/dist/src/ai/tester.d.ts +0 -2
  66. package/dist/src/ai/tester.js +10 -61
  67. package/dist/src/ai/tools.d.ts +18 -7
  68. package/dist/src/ai/tools.js +119 -101
  69. package/dist/src/commands/compact-command.d.ts +1 -1
  70. package/dist/src/commands/compact-command.js +6 -6
  71. package/dist/src/commands/context-command.js +1 -1
  72. package/dist/src/commands/context-knowledge-command.js +1 -1
  73. package/dist/src/commands/experience-command.js +1 -1
  74. package/dist/src/commands/knows-command.js +1 -1
  75. package/dist/src/commands/learn-command.js +3 -1
  76. package/dist/src/components/AddKnowledge.d.ts +2 -0
  77. package/dist/src/components/AddKnowledge.js +3 -7
  78. package/dist/src/components/App.js +2 -2
  79. package/dist/src/config.d.ts +2 -0
  80. package/dist/src/config.js +12 -0
  81. package/dist/src/experience-tracker.d.ts +11 -6
  82. package/dist/src/experience-tracker.js +81 -154
  83. package/dist/src/explorbot.d.ts +4 -5
  84. package/dist/src/explorbot.js +17 -51
  85. package/dist/src/explorer.d.ts +4 -15
  86. package/dist/src/explorer.js +18 -68
  87. package/dist/src/knowledge-tracker.d.ts +2 -2
  88. package/dist/src/knowledge-tracker.js +38 -45
  89. package/dist/src/state-manager.d.ts +6 -31
  90. package/dist/src/state-manager.js +10 -129
  91. package/dist/src/utils/aria.d.ts +6 -1
  92. package/dist/src/utils/aria.js +4 -1
  93. package/dist/src/utils/cache.d.ts +15 -0
  94. package/dist/src/utils/cache.js +34 -0
  95. package/dist/src/utils/context-formatter.d.ts +6 -0
  96. package/dist/src/utils/context-formatter.js +15 -27
  97. package/dist/src/utils/html.d.ts +5 -0
  98. package/dist/src/utils/html.js +71 -0
  99. package/dist/src/utils/markdown-files.d.ts +10 -0
  100. package/dist/src/utils/markdown-files.js +21 -0
  101. package/dist/src/utils/markdown-query.d.ts +6 -0
  102. package/dist/src/utils/markdown-query.js +14 -1
  103. package/dist/src/utils/page-readiness.d.ts +1 -0
  104. package/dist/src/utils/page-readiness.js +1 -1
  105. package/dist/src/utils/secrets.js +2 -2
  106. package/dist/src/utils/strings.d.ts +2 -0
  107. package/dist/src/utils/strings.js +10 -0
  108. package/dist/src/utils/web-element.d.ts +2 -1
  109. package/dist/src/utils/web-element.js +3 -0
  110. package/dist/src/utils/web-sandbox.d.ts +4 -0
  111. package/dist/src/utils/web-sandbox.js +37 -0
  112. package/package.json +20 -3
  113. package/src/action-result.ts +70 -173
  114. package/src/action.ts +16 -130
  115. package/src/ai/captain/web-mode.ts +0 -7
  116. package/src/ai/captain.ts +5 -17
  117. package/src/ai/conversation.ts +9 -32
  118. package/src/ai/driller.ts +3 -16
  119. package/src/ai/experience-compactor.ts +16 -35
  120. package/src/ai/fisherman-tools.ts +1 -1
  121. package/src/ai/fisherman.ts +0 -4
  122. package/src/ai/historian/codeceptjs.ts +4 -2
  123. package/src/ai/historian/playwright.ts +4 -2
  124. package/src/ai/historian/utils.ts +0 -1
  125. package/src/ai/historian.ts +4 -7
  126. package/src/ai/navigator.ts +13 -49
  127. package/src/ai/pilot.ts +6 -16
  128. package/src/ai/planner.ts +56 -44
  129. package/src/ai/provider.ts +85 -145
  130. package/src/ai/quartermaster.ts +34 -41
  131. package/src/ai/rerunner.ts +17 -21
  132. package/src/ai/researcher/cache.ts +10 -16
  133. package/src/ai/researcher/deep-analysis.ts +3 -6
  134. package/src/ai/researcher/parser.ts +1 -1
  135. package/src/ai/researcher/research-result.ts +0 -18
  136. package/src/ai/researcher/sections.ts +5 -1
  137. package/src/ai/researcher.ts +11 -59
  138. package/src/ai/rules.ts +24 -13
  139. package/src/ai/session-analyst.ts +8 -5
  140. package/src/ai/task-agent.ts +3 -30
  141. package/src/ai/tester.ts +12 -73
  142. package/src/ai/tools.ts +119 -106
  143. package/src/commands/compact-command.ts +7 -8
  144. package/src/commands/context-command.ts +1 -1
  145. package/src/commands/context-knowledge-command.ts +1 -1
  146. package/src/commands/experience-command.ts +1 -1
  147. package/src/commands/knows-command.ts +1 -1
  148. package/src/commands/learn-command.ts +3 -1
  149. package/src/components/AddKnowledge.tsx +5 -7
  150. package/src/components/App.tsx +2 -2
  151. package/src/config.ts +12 -0
  152. package/src/experience-tracker.ts +88 -151
  153. package/src/explorbot.ts +17 -51
  154. package/src/explorer.ts +20 -72
  155. package/src/knowledge-tracker.ts +42 -49
  156. package/src/state-manager.ts +15 -156
  157. package/src/utils/aria.ts +11 -2
  158. package/src/utils/cache.ts +40 -0
  159. package/src/utils/context-formatter.ts +12 -15
  160. package/src/utils/html.ts +79 -0
  161. package/src/utils/markdown-files.ts +30 -0
  162. package/src/utils/markdown-query.ts +15 -1
  163. package/src/utils/page-readiness.ts +1 -1
  164. package/src/utils/secrets.ts +2 -3
  165. package/src/utils/strings.ts +12 -0
  166. package/src/utils/web-element.ts +4 -0
  167. package/src/utils/web-sandbox.ts +43 -0
  168. package/dist/boat/api-tester/example/apibot.config.js +0 -30
@@ -136,7 +136,7 @@ function extractKeyFields(body, result = {}, depth = 0) {
136
136
  return result;
137
137
  }
138
138
  for (const [key, value] of Object.entries(body)) {
139
- if (value === null || value === undefined)
139
+ if (value == null)
140
140
  continue;
141
141
  if (typeof value === 'object') {
142
142
  extractKeyFields(value, result, depth + 1);
@@ -18,7 +18,6 @@ export declare class Fisherman implements Agent {
18
18
  hasApiConfig: boolean;
19
19
  constructor(provider: Provider, apiClient: ApiClient, requestStore: RequestStore, specLoader: () => Promise<any | null>, baseEndpoint: string, cookieProvider: () => Promise<Record<string, string>>, configHeaders?: Record<string, string>, hasApiConfig?: boolean);
20
20
  isAvailable(): boolean;
21
- getMode(): string;
22
21
  ensureReady(scopeUrl?: string): Promise<void>;
23
22
  getEndpointList(scopeUrl?: string): string;
24
23
  prepareData(instructions: string, scopeUrl?: string, sessionName?: string): Promise<FishermanResult>;
@@ -34,9 +34,6 @@ export class Fisherman {
34
34
  isAvailable() {
35
35
  return this.mode !== 'disabled';
36
36
  }
37
- getMode() {
38
- return this.mode;
39
- }
40
37
  async ensureReady(scopeUrl) {
41
38
  await this.detectMode(scopeUrl);
42
39
  this.spec ??= await this.specLoader();
@@ -1,3 +1,4 @@
1
+ import type { StateManager } from '../../state-manager.js';
1
2
  import type { Plan } from '../../test-plan.js';
2
3
  import type { Conversation } from '../conversation.js';
3
4
  import type { Constructor } from './mixin.js';
@@ -8,6 +9,7 @@ export interface CodeceptJSMethods {
8
9
  export declare function WithCodeceptJS<T extends Constructor>(Base: T): {
9
10
  new (...args: any[]): {
10
11
  savedFiles: Set<string>;
12
+ stateManager?: StateManager;
11
13
  toCode(conversation: Conversation, scenario: string): string;
12
14
  saveCodeceptPlanToFile(plan: Plan): string;
13
15
  getKnowledgeLines(url: string, indent?: string): string[];
@@ -2,7 +2,6 @@ import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { ActionResult } from "../../action-result.js";
4
4
  import { ConfigParser } from "../../config.js";
5
- import { KnowledgeTracker } from "../../knowledge-tracker.js";
6
5
  import { tag } from "../../utils/logger.js";
7
6
  import { relativeToCwd } from "../../utils/next-steps.js";
8
7
  import { safeFilename } from "../../utils/strings.js";
@@ -87,7 +86,9 @@ export function WithCodeceptJS(Base) {
87
86
  return filePath;
88
87
  }
89
88
  getKnowledgeLines(url, indent = ' ') {
90
- const knowledgeTracker = new KnowledgeTracker();
89
+ const knowledgeTracker = this.stateManager?.getKnowledgeTracker();
90
+ if (!knowledgeTracker)
91
+ return [];
91
92
  const state = new ActionResult({ url });
92
93
  const { wait, waitForElement, code } = knowledgeTracker.getStateParameters(state, ['wait', 'waitForElement', 'code']);
93
94
  const lines = [];
@@ -1,3 +1,4 @@
1
+ import type { StateManager } from '../../state-manager.js';
1
2
  import { type PlaywrightRecorder } from '../../playwright-recorder.js';
2
3
  import type { Plan } from '../../test-plan.js';
3
4
  import type { Conversation } from '../conversation.js';
@@ -13,6 +14,7 @@ export declare function WithPlaywright<T extends Constructor>(Base: T): {
13
14
  helper: any;
14
15
  } | undefined;
15
16
  savedFiles: Set<string>;
17
+ stateManager?: StateManager;
16
18
  toPlaywrightCode(conversation: Conversation, scenario: string): Promise<string>;
17
19
  savePlaywrightPlanToFile(plan: Plan): string;
18
20
  getPlaywrightKnowledgeLines(url: string, indent?: string): string[];
@@ -2,7 +2,6 @@ import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { ActionResult } from "../../action-result.js";
4
4
  import { ConfigParser } from "../../config.js";
5
- import { KnowledgeTracker } from "../../knowledge-tracker.js";
6
5
  import { renderAssertion, renderCall } from "../../playwright-recorder.js";
7
6
  import { tag } from "../../utils/logger.js";
8
7
  import { relativeToCwd } from "../../utils/next-steps.js";
@@ -124,7 +123,9 @@ export function WithPlaywright(Base) {
124
123
  return filePath;
125
124
  }
126
125
  getPlaywrightKnowledgeLines(url, indent = ' ') {
127
- const knowledgeTracker = new KnowledgeTracker();
126
+ const knowledgeTracker = this.stateManager?.getKnowledgeTracker();
127
+ if (!knowledgeTracker)
128
+ return [];
128
129
  const state = new ActionResult({ url });
129
130
  const { wait, waitForElement } = knowledgeTracker.getStateParameters(state, ['wait', 'waitForElement']);
130
131
  const lines = [];
@@ -1,4 +1,3 @@
1
1
  import type { ToolExecution } from '../conversation.js';
2
- export { isNonReusableCode, stripComments } from '../../utils/step-analyzer.js';
3
2
  export declare function escapeString(str: string): string;
4
3
  export declare function getExecutionLabel(exec: ToolExecution, fallback?: string): string;
@@ -1,4 +1,3 @@
1
- export { isNonReusableCode, stripComments } from "../../utils/step-analyzer.js";
2
1
  export function escapeString(str) {
3
2
  return str.replace(/'/g, "\\'").replace(/\n/g, ' ');
4
3
  }
@@ -1,5 +1,5 @@
1
1
  import type { ExplorbotConfig } from '../config.js';
2
- import { ExperienceTracker } from '../experience-tracker.js';
2
+ import type { ExperienceTracker } from '../experience-tracker.js';
3
3
  import type { PlaywrightRecorder } from '../playwright-recorder.js';
4
4
  import type { Reporter } from '../reporter.js';
5
5
  import type { StateManager } from '../state-manager.js';
@@ -9,7 +9,6 @@ import { type ExperienceMethods } from './historian/experience.js';
9
9
  import { type PlaywrightMethods } from './historian/playwright.js';
10
10
  import { type ScreencastMethods } from './historian/screencast.js';
11
11
  import type { Provider } from './provider.js';
12
- export { isNonReusableCode } from '../utils/step-analyzer.js';
13
12
  declare const HistorianBase: {
14
13
  new (...args: any[]): {
15
14
  config: ExplorbotConfig | undefined;
@@ -40,6 +39,7 @@ declare const HistorianBase: {
40
39
  helper: any;
41
40
  };
42
41
  savedFiles: Set<string>;
42
+ stateManager?: StateManager;
43
43
  toPlaywrightCode(conversation: import("./conversation.js").Conversation, scenario: string): Promise<string>;
44
44
  savePlaywrightPlanToFile(plan: Plan): string;
45
45
  getPlaywrightKnowledgeLines(url: string, indent?: string): string[];
@@ -47,6 +47,7 @@ declare const HistorianBase: {
47
47
  } & {
48
48
  new (...args: any[]): {
49
49
  savedFiles: Set<string>;
50
+ stateManager?: StateManager;
50
51
  toCode(conversation: import("./conversation.js").Conversation, scenario: string): string;
51
52
  saveCodeceptPlanToFile(plan: Plan): string;
52
53
  getKnowledgeLines(url: string, indent?: string): string[];
@@ -93,7 +94,7 @@ export declare class Historian extends HistorianBase {
93
94
  helper: any;
94
95
  } | undefined;
95
96
  savedFiles: Set<string>;
96
- constructor(provider: Provider, experienceTracker?: ExperienceTracker, reporter?: Reporter, stateManager?: StateManager, config?: ExplorbotConfig, playwright?: {
97
+ constructor(provider: Provider, experienceTracker: ExperienceTracker, reporter?: Reporter, stateManager?: StateManager, config?: ExplorbotConfig, playwright?: {
97
98
  recorder: PlaywrightRecorder;
98
99
  helper: any;
99
100
  });
@@ -101,8 +102,8 @@ export declare class Historian extends HistorianBase {
101
102
  getSavedFiles(): string[];
102
103
  savePlanToFile(plan: Plan): string;
103
104
  rewriteScenarioInFile(filePath: string, healedSteps: Array<{
104
- test: string;
105
105
  original: string;
106
106
  healed: string;
107
107
  }>): void;
108
108
  }
109
+ export {};
@@ -1,18 +1,16 @@
1
1
  import { readFileSync, writeFileSync } from 'node:fs';
2
- import { ExperienceTracker } from "../experience-tracker.js";
3
2
  import { tag } from "../utils/logger.js";
4
3
  import { relativeToCwd } from "../utils/next-steps.js";
5
4
  import { WithCodeceptJS } from "./historian/codeceptjs.js";
6
5
  import { WithExperience } from "./historian/experience.js";
7
6
  import { WithPlaywright } from "./historian/playwright.js";
8
7
  import { WithScreencast } from "./historian/screencast.js";
9
- export { isNonReusableCode } from "../utils/step-analyzer.js";
10
8
  const HistorianBase = WithScreencast(WithPlaywright(WithCodeceptJS(WithExperience(Object))));
11
9
  export class Historian extends HistorianBase {
12
10
  constructor(provider, experienceTracker, reporter, stateManager, config, playwright) {
13
11
  super();
14
12
  this.provider = provider;
15
- this.experienceTracker = experienceTracker || new ExperienceTracker();
13
+ this.experienceTracker = experienceTracker;
16
14
  this.reporter = reporter;
17
15
  this.stateManager = stateManager;
18
16
  this.config = config;
@@ -32,8 +30,6 @@ export class Historian extends HistorianBase {
32
30
  rewriteScenarioInFile(filePath, healedSteps) {
33
31
  let content = readFileSync(filePath, 'utf-8');
34
32
  for (const step of healedSteps) {
35
- if (!content.includes(step.original))
36
- continue;
37
33
  content = content.replace(step.original, step.healed);
38
34
  }
39
35
  writeFileSync(filePath, content);
@@ -1,16 +1,14 @@
1
1
  import { ActionResult } from '../action-result.js';
2
2
  import type Action from '../action.js';
3
- import { ExperienceTracker } from '../experience-tracker.js';
3
+ import type { ExperienceTracker } from '../experience-tracker.js';
4
4
  import Explorer from '../explorer.js';
5
- import { KnowledgeTracker } from '../knowledge-tracker.js';
5
+ import type { KnowledgeTracker } from '../knowledge-tracker.js';
6
6
  import { HooksRunner } from '../utils/hooks-runner.js';
7
7
  import type { Agent } from './agent.js';
8
- import { ExperienceCompactor } from './experience-compactor.js';
9
8
  import type { Provider } from './provider.js';
10
9
  declare class Navigator implements Agent {
11
10
  emoji: string;
12
11
  provider: Provider;
13
- experienceCompactor: ExperienceCompactor;
14
12
  knowledgeTracker: KnowledgeTracker;
15
13
  experienceTracker: ExperienceTracker;
16
14
  hooksRunner: HooksRunner;
@@ -18,7 +16,7 @@ declare class Navigator implements Agent {
18
16
  systemPrompt: string;
19
17
  freeSailSystemPrompt: string;
20
18
  explorer: Explorer;
21
- constructor(explorer: Explorer, provider: Provider, experienceCompactor: ExperienceCompactor, experienceTracker?: ExperienceTracker);
19
+ constructor(explorer: Explorer, provider: Provider);
22
20
  get verifyAttempts(): number;
23
21
  get verifyTimeout(): number;
24
22
  getBaseOrigin(): string | null;
@@ -2,8 +2,6 @@ import { tool } from 'ai';
2
2
  import dedent from 'dedent';
3
3
  import { z } from 'zod';
4
4
  import { ActionResult } from '../action-result.js';
5
- import { ExperienceTracker, renderExperienceToc } from '../experience-tracker.js';
6
- import { KnowledgeTracker } from '../knowledge-tracker.js';
7
5
  import { normalizeUrl } from '../state-manager.js';
8
6
  import { extractCodeBlocks } from '../utils/code-extractor.js';
9
7
  import { HooksRunner } from "../utils/hooks-runner.js";
@@ -14,12 +12,11 @@ import { extractStatePath } from '../utils/url-matcher.js';
14
12
  import { Researcher } from "./researcher.js";
15
13
  import { actionRule, locatorRule, unexpectedPopupRule } from './rules.js';
16
14
  import { isInteractive } from './task-agent.js';
17
- import { createAgentTools } from "./tools.js";
15
+ import { createLearnExperienceTool } from "./tools.js";
18
16
  const debugLog = createDebug('explorbot:navigator');
19
17
  class Navigator {
20
18
  emoji = '🧭';
21
19
  provider;
22
- experienceCompactor;
23
20
  knowledgeTracker;
24
21
  experienceTracker;
25
22
  hooksRunner;
@@ -62,12 +59,11 @@ class Navigator {
62
59
  </rules>
63
60
  `;
64
61
  explorer;
65
- constructor(explorer, provider, experienceCompactor, experienceTracker) {
62
+ constructor(explorer, provider) {
66
63
  this.provider = provider;
67
64
  this.explorer = explorer;
68
- this.experienceCompactor = experienceCompactor;
69
- this.knowledgeTracker = new KnowledgeTracker();
70
- this.experienceTracker = experienceTracker || new ExperienceTracker();
65
+ this.knowledgeTracker = explorer.getKnowledgeTracker();
66
+ this.experienceTracker = explorer.getStateManager().getExperienceTracker();
71
67
  this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
72
68
  }
73
69
  get verifyAttempts() {
@@ -171,17 +167,8 @@ class Navigator {
171
167
  debugLog('Resolution message:', message);
172
168
  const action = opts?.action ?? this.explorer.createAction();
173
169
  const expectedUrl = opts?.expectedUrl;
174
- let knowledge = '';
170
+ const knowledge = this.knowledgeTracker.renderRelevantKnowledge(actionResult);
175
171
  let experience = '';
176
- const relevantKnowledge = this.knowledgeTracker.getRelevantKnowledge(actionResult);
177
- if (relevantKnowledge.length > 0) {
178
- const knowledgeContent = relevantKnowledge.map((k) => k.content).join('\n\n');
179
- knowledge = `
180
- <hint>
181
- Here is relevant knowledge for this page:
182
- ${knowledgeContent}
183
- </hint>`;
184
- }
185
172
  if (!actionResult.isInsideIframe) {
186
173
  const successful = this.experienceTracker.getSuccessfulExperience(actionResult);
187
174
  if (successful.length > 0) {
@@ -362,7 +349,6 @@ class Navigator {
362
349
  if (freshState.getStateHash() !== prevHash) {
363
350
  try {
364
351
  const diff = await freshState.diff(prevActionResult);
365
- await diff.calculate();
366
352
  ariaChanges = diff.ariaChanged;
367
353
  }
368
354
  catch (err) {
@@ -446,21 +432,14 @@ class Navigator {
446
432
  return resolved;
447
433
  }
448
434
  buildExperienceTools() {
435
+ if (!this.experienceTracker)
436
+ return undefined;
449
437
  const stateManager = this.explorer.getStateManager();
450
438
  const getState = () => {
451
439
  const s = stateManager.getCurrentState();
452
440
  return s ? ActionResult.fromState(s) : null;
453
441
  };
454
- const { learnExperience } = createAgentTools({
455
- explorer: this.explorer,
456
- researcher: null,
457
- navigator: this,
458
- experienceTracker: this.experienceTracker,
459
- getState,
460
- });
461
- if (!learnExperience)
462
- return undefined;
463
- return { learnExperience };
442
+ return { learnExperience: createLearnExperienceTool({ getExperienceTracker: () => this.experienceTracker, getState }) };
464
443
  }
465
444
  async freeSail(opts, actionResult) {
466
445
  const stateManager = this.explorer.getStateManager();
@@ -579,24 +558,10 @@ class Navigator {
579
558
  tag('operation').log(`Reusing cached verification: ${cachedVerification ? 'PASS' : 'FAIL'}`);
580
559
  return { verified: cachedVerification, successfulCodes: [], assertionSteps: [], totalAttempted: 0 };
581
560
  }
582
- let knowledge = '';
561
+ const knowledge = this.knowledgeTracker.renderRelevantKnowledge(actionResult);
583
562
  let experience = '';
584
- const relevantKnowledge = this.knowledgeTracker.getRelevantKnowledge(actionResult);
585
- if (relevantKnowledge.length > 0) {
586
- const knowledgeContent = relevantKnowledge.map((k) => k.content).join('\n\n');
587
- knowledge = `
588
- <hint>
589
- Here is relevant knowledge for this page:
590
- ${knowledgeContent}
591
- </hint>`;
592
- }
593
563
  if (!actionResult.isInsideIframe) {
594
- const toc = this.experienceTracker.getExperienceTableOfContents(actionResult);
595
- if (toc.length > 0) {
596
- const totalSections = toc.reduce((sum, entry) => sum + entry.sections.length, 0);
597
- tag('operation').log(`Found ${toc.length} experience ${pluralize(toc.length, 'file')} (${totalSections} sections) for: ${actionResult.url}`);
598
- experience = renderExperienceToc(toc);
599
- }
564
+ experience = this.experienceTracker.renderExperienceTocFor(actionResult);
600
565
  }
601
566
  const priorVerifications = Object.entries(actionResult.verifications ?? {});
602
567
  let verificationContext = '';
@@ -685,7 +650,7 @@ class Navigator {
685
650
  return;
686
651
  }
687
652
  await this.explorer.switchToMainFrame();
688
- const verified = await action.attempt(codeBlock, message, false);
653
+ const verified = await action.attempt(codeBlock, message);
689
654
  if (verified) {
690
655
  tag('success').log('Verification passed');
691
656
  successfulCodes.push(codeBlock);
@@ -1,5 +1,4 @@
1
1
  import { ActionResult } from '../action-result.js';
2
- import { type ExperienceTracker } from '../experience-tracker.js';
3
2
  import type Explorer from '../explorer.js';
4
3
  import { type Test } from '../test-plan.js';
5
4
  import type { Agent } from './agent.js';
@@ -16,8 +15,7 @@ export declare class Pilot implements Agent {
16
15
  researcher: Researcher;
17
16
  explorer: Explorer;
18
17
  fisherman: Fisherman | null;
19
- experienceTracker: ExperienceTracker | null;
20
- constructor(provider: Provider, agentTools: any, researcher: Researcher, explorer: Explorer, experienceTracker?: ExperienceTracker);
18
+ constructor(provider: Provider, agentTools: any, researcher: Researcher, explorer: Explorer);
21
19
  setFisherman(fisherman: Fisherman): void;
22
20
  get stepsToReview(): number;
23
21
  reset(): void;
@@ -3,7 +3,6 @@ import dedent from 'dedent';
3
3
  import { z } from 'zod';
4
4
  import { ActionResult } from "../action-result.js";
5
5
  import { ConfigParser } from "../config.js";
6
- import { renderExperienceToc } from "../experience-tracker.js";
7
6
  import { TestResult } from "../test-plan.js";
8
7
  import { collectInteractiveNodes, detectFocusArea, extractFocusedElement } from "../utils/aria.js";
9
8
  import { ErrorPageError } from "../utils/error-page.js";
@@ -22,13 +21,11 @@ export class Pilot {
22
21
  researcher;
23
22
  explorer;
24
23
  fisherman = null;
25
- experienceTracker;
26
- constructor(provider, agentTools, researcher, explorer, experienceTracker) {
24
+ constructor(provider, agentTools, researcher, explorer) {
27
25
  this.provider = provider;
28
26
  this.agentTools = agentTools;
29
27
  this.researcher = researcher;
30
28
  this.explorer = explorer;
31
- this.experienceTracker = experienceTracker || null;
32
29
  }
33
30
  setFisherman(fisherman) {
34
31
  this.fisherman = fisherman;
@@ -90,7 +87,7 @@ export class Pilot {
90
87
  requestVerification: z
91
88
  .string()
92
89
  .nullable()
93
- .describe('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 test suite \\"Foo\\" is visible in the suites 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".'),
90
+ .describe('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".'),
94
91
  });
95
92
  const userContent = dedent `
96
93
  Tester wants to ${type} the test.
@@ -124,7 +121,7 @@ export class Pilot {
124
121
  - Mixed evidence + final state shows success → pass. Mixed + final state unclear → continue with guidance.
125
122
 
126
123
  When deciding "pass", you MUST also set requestVerification to a one-sentence natural-language
127
- claim about the current page (e.g., "New test suite Foo is visible in the suites list"). NOT
124
+ claim about the current page (e.g., "New item Foo is visible in the items list"). NOT
128
125
  code — do not write I.*, expect(), .then(), or any JavaScript. Choose the strongest single
129
126
  piece of evidence (a unique element/text that exists ONLY because the scenario succeeded).
130
127
  Navigator translates the claim into CodeceptJS assertions; without it the generated test has
@@ -379,10 +376,6 @@ export class Pilot {
379
376
 
380
377
  FIRST: Decide if precondition() is needed.
381
378
 
382
- ${capabilityGroundingRule}
383
-
384
- ${dataProtectionRules}
385
-
386
379
  Call precondition() WHEN:
387
380
  - The scenario edits/deletes/modifies an item, and you want a DISPOSABLE item to act on safely
388
381
  - The scenario needs specific data clearly NOT on the current page (e.g., items with specific statuses for filtering)
@@ -525,14 +518,10 @@ export class Pilot {
525
518
  `;
526
519
  }
527
520
  getExperienceToc() {
528
- if (!this.experienceTracker)
529
- return '';
530
521
  const state = this.explorer.getStateManager().getCurrentState();
531
522
  if (!state)
532
523
  return '';
533
- const actionResult = ActionResult.fromState(state);
534
- const toc = this.experienceTracker.getExperienceTableOfContents(actionResult);
535
- return renderExperienceToc(toc);
524
+ return this.explorer.getStateManager().getExperienceTracker().renderExperienceTocFor(ActionResult.fromState(state));
536
525
  }
537
526
  pickPlanningTools() {
538
527
  const { see, context, verify, research, getVisitedStates, xpathCheck, learnExperience, askUser } = this.agentTools ?? {};
@@ -957,8 +946,8 @@ export class Pilot {
957
946
 
958
947
  ${dataProtectionRules}
959
948
 
960
- Describe WHAT to create, not what exists. RIGHT: precondition("1 test"). WRONG:
961
- precondition("1 test suite named Updated Suite with existing tests"). Keep descriptions short.
949
+ Describe WHAT to create, not what exists. RIGHT: precondition("1 post"). WRONG:
950
+ precondition("1 post named Updated Post with existing comments"). Keep descriptions short.
962
951
 
963
952
  Response format:
964
953
  PROGRESS: <1 sentence assessment>
@@ -1,5 +1,5 @@
1
1
  import { ActionResult } from '../action-result.js';
2
- import { ExperienceTracker } from '../experience-tracker.js';
2
+ import type { ExperienceTracker } from '../experience-tracker.js';
3
3
  import type Explorer from '../explorer.js';
4
4
  import type { StateManager } from '../state-manager.js';
5
5
  import { Suite } from '../suite.js';
@@ -8,7 +8,7 @@ import type { Agent } from './agent.js';
8
8
  import { Conversation } from './conversation.js';
9
9
  import type { Fisherman } from './fisherman.js';
10
10
  import type { Provider } from './provider.js';
11
- import { Researcher } from './researcher.js';
11
+ import { type Researcher } from './researcher.js';
12
12
  declare const PlannerBase: {
13
13
  new (...args: any[]): {
14
14
  currentPlan: Plan | null;
@@ -58,7 +58,7 @@ export declare class Planner extends PlannerBase implements Agent {
58
58
  lastSuite: Suite | null;
59
59
  researcher: Researcher;
60
60
  fisherman: Fisherman | null;
61
- constructor(explorer: Explorer, provider: Provider);
61
+ constructor(explorer: Explorer, provider: Provider, researcher: Researcher);
62
62
  setFisherman(fisherman: Fisherman): void;
63
63
  get sectionOrder(): string[];
64
64
  getDefaultStartUrl(state: {
@@ -3,7 +3,6 @@ import { z } from 'zod';
3
3
  import { ActionResult } from "../action-result.js";
4
4
  import { setActivity } from "../activity.js";
5
5
  import { ConfigParser } from "../config.js";
6
- import { ExperienceTracker } from "../experience-tracker.js";
7
6
  import { Observability } from "../observability.js";
8
7
  import { Stats } from "../stats.js";
9
8
  import { Suite } from "../suite.js";
@@ -16,7 +15,7 @@ import { Conversation } from "./conversation.js";
16
15
  import { WithSessionDedup } from "./planner/session-dedup.js";
17
16
  import { getActiveStyle } from "./planner/styles.js";
18
17
  import { WithSubPages, getPlannedByStateHash, getRegisteredPlan, registerPlan } from "./planner/subpages.js";
19
- import { POSSIBLE_SECTIONS, Researcher } from "./researcher.js";
18
+ import { POSSIBLE_SECTIONS } from "./researcher.js";
20
19
  import { findSimilarStateHash } from "./researcher/cache.js";
21
20
  import { hasFocusedSection } from "./researcher/focus.js";
22
21
  import { capabilityGroundingRule, dataProtectionRules, fileUploadRule } from "./rules.js";
@@ -50,13 +49,13 @@ export class Planner extends PlannerBase {
50
49
  lastSuite = null;
51
50
  researcher;
52
51
  fisherman = null;
53
- constructor(explorer, provider) {
52
+ constructor(explorer, provider, researcher) {
54
53
  super();
55
54
  this.explorer = explorer;
56
55
  this.provider = provider;
57
- this.researcher = new Researcher(explorer, provider);
56
+ this.researcher = researcher;
58
57
  this.stateManager = explorer.getStateManager();
59
- this.experienceTracker = new ExperienceTracker();
58
+ this.experienceTracker = explorer.getStateManager().getExperienceTracker();
60
59
  }
61
60
  setFisherman(fisherman) {
62
61
  this.fisherman = fisherman;
@@ -81,7 +80,7 @@ export class Planner extends PlannerBase {
81
80
  You are ISTQB certified senior manual QA planning exploratory testing session of a web application.
82
81
  Each test scenario must complete a meaningful user workflow — not just open a UI element and verify it exists.
83
82
  Bad: "Open the Help panel" — just opens something.
84
- Good: "Create a new suite and verify it appears in the list" — completes a business action.
83
+ Good: "Create a new post and verify it appears in the list" — completes a business action.
85
84
  </role>
86
85
  <task>
87
86
  List possible testing scenarios for the web page.
@@ -149,6 +148,7 @@ export class Planner extends PlannerBase {
149
148
  const tags = ['planner'];
150
149
  if (style)
151
150
  tags.push(style);
151
+ const existingTestScenarios = this.getExistingTestFileScenarios(state.url);
152
152
  const result = await Observability.run(`planner: ${state.url}`, { tags, sessionId: state.url }, async () => {
153
153
  const actionResult = ActionResult.fromState(state);
154
154
  const conversation = await this.buildConversation(actionResult, style, parentPlan, feature);
@@ -182,7 +182,6 @@ export class Planner extends PlannerBase {
182
182
  if (parentPlan)
183
183
  this.currentPlan.parentPlan = parentPlan;
184
184
  const allPreviousScenarios = this.getPreviousSessionScenarios();
185
- const existingTestScenarios = this.getExistingTestFileScenarios(state.url);
186
185
  for (const s of existingTestScenarios)
187
186
  allPreviousScenarios.add(s);
188
187
  for (const t of tests) {
@@ -232,6 +231,7 @@ export class Planner extends PlannerBase {
232
231
  return added;
233
232
  }
234
233
  getExistingTestFileScenarios(currentUrl) {
234
+ this.lastSuite = null;
235
235
  if (!currentUrl)
236
236
  return new Set();
237
237
  try {
@@ -244,27 +244,37 @@ export class Planner extends PlannerBase {
244
244
  }
245
245
  }
246
246
  cleanExperienceFlows(text) {
247
- const seenTitles = new Set();
248
247
  let result = text;
249
- for (const section of [...mdq(text).query('section2').each(), ...mdq(text).query('section3').each()]) {
250
- const heading = section.query('heading').text().trim();
251
- const body = mdq(section.text())
252
- .query('heading')
253
- .replace('')
254
- .replace(/^---\s*$/gm, '')
255
- .trim();
256
- if (!body || seenTitles.has(heading)) {
257
- result = result.replace(section.text(), '');
258
- continue;
259
- }
260
- seenTitles.add(heading);
261
- const blockquotes = section.query('blockquote').each();
262
- if (blockquotes.length <= 10)
263
- continue;
264
- for (const bq of blockquotes.slice(10)) {
265
- result = result.replace(bq.text(), '');
266
- }
267
- result = result.replace(section.text().trim(), `${section.text().trim()}\n> ... and ${blockquotes.length - 10} more discoveries`);
248
+ const seenTitles = new Set();
249
+ for (const selector of ['section2', 'section3']) {
250
+ result = mdq(result)
251
+ .query(selector)
252
+ .replaceEach((section) => {
253
+ const heading = section.query('heading').text().trim();
254
+ const withoutHeadings = mdq(section.text()).query('heading').replace('');
255
+ const body = mdq(withoutHeadings).query('hr').replace('').trim();
256
+ if (body && !seenTitles.has(heading)) {
257
+ seenTitles.add(heading);
258
+ return section.text();
259
+ }
260
+ return '';
261
+ });
262
+ }
263
+ const trimmedTitles = new Set();
264
+ for (const selector of ['section2', 'section3']) {
265
+ result = mdq(result)
266
+ .query(selector)
267
+ .replaceEach((section) => {
268
+ const heading = section.query('heading').text().trim();
269
+ if (trimmedTitles.has(heading))
270
+ return section.text();
271
+ const count = section.query('blockquote').count();
272
+ if (count <= 10)
273
+ return section.text();
274
+ const kept = mdq(section.text()).query('blockquote[10:]').replace('');
275
+ trimmedTitles.add(heading);
276
+ return `${kept.trimEnd()}\n> ... and ${count - 10} more discoveries\n`;
277
+ });
268
278
  }
269
279
  return result.trim() || null;
270
280
  }
@@ -298,6 +308,7 @@ export class Planner extends PlannerBase {
298
308
  If there are subpages (pages with same URL path) plan testing of those subpages as well
299
309
  If you plan to test CRUD operations, plan them in correct order: create, read, update.
300
310
  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.
311
+ When validation placement or wording was not observed, require feedback associated with the invalid input without inventing a specific location or message.
301
312
  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.
302
313
  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.
303
314
  Only propose scenarios whose prerequisites are evident from page research, visited pages, or API data preparation context.
@@ -337,17 +348,18 @@ export class Planner extends PlannerBase {
337
348
  deep: true,
338
349
  });
339
350
  let plannerResearch = mdq(research).query('code').replace('');
340
- for (const table of mdq(plannerResearch).query('table').each()) {
341
- const rawTable = table.text();
351
+ plannerResearch = mdq(plannerResearch)
352
+ .query('table')
353
+ .replaceEach((table) => {
342
354
  const rows = table.toJson();
343
355
  if (rows.length === 0 || !rows[0].Element)
344
- continue;
356
+ return table.text();
345
357
  const elementWithType = rows.map((r) => ({
346
358
  Element: r.Element,
347
359
  Type: r.Type || '',
348
360
  }));
349
- plannerResearch = plannerResearch.replace(rawTable, jsonToTable(elementWithType, ['Element', 'Type']));
350
- }
361
+ return jsonToTable(elementWithType, ['Element', 'Type']);
362
+ });
351
363
  const hasFocusedOverlay = hasFocusedSection(plannerResearch);
352
364
  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." : '';
353
365
  const featureFilter = feature ? `FOCUS FILTER: Only propose scenarios using elements relevant to "${feature}". Ignore all other elements.` : '';
@@ -525,17 +537,17 @@ export class Planner extends PlannerBase {
525
537
 
526
538
  STEPS - actionable commands:
527
539
  - Each step should be a specific action to perform
528
- - Good: "Click Create Suite button", "Enter 'My New Suite' as suite name", "Click Save button"
529
- - Good: "Click Star icon on 'Template API Testing' suite"
540
+ - Good: "Click Create Post button", "Enter 'My New Post' as post title", "Click Save button"
541
+ - Good: "Click Star icon on 'Quarterly Report' item"
530
542
  - Bad: "Open the dropdown menu" (opening is not a goal, it's a means)
531
543
  - Bad: "Verify modal appears" (verification belongs in expected outcomes, not steps)
532
544
  - Steps should form a complete workflow, not stop at opening a UI element
533
545
  - NEVER split a workflow across two tests. One test = one complete action + its verification
534
546
 
535
547
  EXPECTED OUTCOMES - verifiable results:
536
- - Good: "New suite 'My New Suite' appears in the suite list"
537
- - Good: "Suite appears under Starred filter tab"
538
- - Good: "Success message 'Suite created' is displayed"
548
+ - Good: "New post 'My New Post' appears in the posts list"
549
+ - Good: "Item appears under Starred filter tab"
550
+ - Good: "Success message 'Post created' is displayed"
539
551
  - Good when wording is unknown: "An authentication error is displayed"
540
552
  - Good when route is unknown: "The workspace home page is displayed"
541
553
  - Bad: "Modal is displayed" (just verifying existence, no business value)