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
package/dist/src/explorer.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { AIProvider } from './ai/provider.js';
|
|
|
5
5
|
import { RequestStore } from './api/request-store.js';
|
|
6
6
|
import { XhrCapture } from './api/xhr-capture.js';
|
|
7
7
|
import type { ExplorbotConfig } from './config.js';
|
|
8
|
-
import type {
|
|
8
|
+
import type { ExperienceTracker } from './experience-tracker.js';
|
|
9
9
|
import { KnowledgeTracker } from './knowledge-tracker.js';
|
|
10
10
|
import { PlaywrightRecorder } from './playwright-recorder.js';
|
|
11
11
|
import { Reporter } from './reporter.js';
|
|
@@ -42,7 +42,6 @@ declare class Explorer {
|
|
|
42
42
|
stateManager: StateManager;
|
|
43
43
|
knowledgeTracker: KnowledgeTracker;
|
|
44
44
|
config: ExplorbotConfig;
|
|
45
|
-
userResolveFn: UserResolveFunction | null;
|
|
46
45
|
options?: {
|
|
47
46
|
show?: boolean;
|
|
48
47
|
headless?: boolean;
|
|
@@ -59,19 +58,17 @@ declare class Explorer {
|
|
|
59
58
|
testPageErrorHandler: ((error: Error) => void) | null;
|
|
60
59
|
testConsoleHandler: ((message: any) => void) | null;
|
|
61
60
|
testDialogHandler: ((dialog: any) => void) | null;
|
|
62
|
-
constructor(config: ExplorbotConfig, aiProvider: AIProvider, options
|
|
61
|
+
constructor(config: ExplorbotConfig, aiProvider: AIProvider, options: {
|
|
63
62
|
show?: boolean;
|
|
64
63
|
headless?: boolean;
|
|
65
64
|
incognito?: boolean;
|
|
66
65
|
session?: string;
|
|
67
|
-
});
|
|
66
|
+
} | undefined, experienceTracker: ExperienceTracker, knowledgeTracker: KnowledgeTracker);
|
|
68
67
|
initializeContainer(): void;
|
|
69
68
|
convertToCodeceptConfig(config: ExplorbotConfig): any;
|
|
70
69
|
getConfig(): ExplorbotConfig;
|
|
71
|
-
getConfigPath(): string | null;
|
|
72
70
|
getAIProvider(): AIProvider;
|
|
73
71
|
getStateManager(): StateManager;
|
|
74
|
-
getCurrentUrl(): string;
|
|
75
72
|
getKnowledgeTracker(): KnowledgeTracker;
|
|
76
73
|
getReporter(): Reporter;
|
|
77
74
|
getRequestStore(): RequestStore | null;
|
|
@@ -87,7 +84,7 @@ declare class Explorer {
|
|
|
87
84
|
}): Promise<ActionResult>;
|
|
88
85
|
capturePageWithScreenshot(): Promise<ActionResult>;
|
|
89
86
|
executeAction(code: string): Promise<Action>;
|
|
90
|
-
attemptAction(code: string, originalMessage?: string
|
|
87
|
+
attemptAction(code: string, originalMessage?: string): Promise<boolean>;
|
|
91
88
|
getPlaywrightRecorder(): PlaywrightRecorder;
|
|
92
89
|
visit(url: string): Promise<Action>;
|
|
93
90
|
visitOnce(url: string): Promise<Action>;
|
|
@@ -100,7 +97,6 @@ declare class Explorer {
|
|
|
100
97
|
}): Promise<number>;
|
|
101
98
|
getEidxInContainer(containerCss: string | null): Promise<string[]>;
|
|
102
99
|
getEidxByLocator(locator: string, container?: string | null): Promise<string | null>;
|
|
103
|
-
reload(): Promise<void>;
|
|
104
100
|
resolveBrowserUrl(url?: string): string | null;
|
|
105
101
|
isFatalBrowserError(error: unknown): boolean;
|
|
106
102
|
recoverFromBrowserError(): Promise<boolean>;
|
|
@@ -112,22 +108,15 @@ declare class Explorer {
|
|
|
112
108
|
hasOtherTabs(): boolean;
|
|
113
109
|
getOtherTabsInfo(): TabInfo[];
|
|
114
110
|
clearOtherTabsInfo(): void;
|
|
115
|
-
setUserResolve(userResolveFn: UserResolveFunction): void;
|
|
116
111
|
listenToStateChanged(): void;
|
|
117
112
|
stop(): Promise<void>;
|
|
118
113
|
get activeTest(): Test | null;
|
|
119
114
|
startTest(test: Test): Promise<boolean>;
|
|
120
115
|
ensurePageAvailable(): Promise<boolean>;
|
|
121
|
-
ensureActiveTestPageAvailable(): Promise<boolean>;
|
|
122
116
|
handleExecutionError(error: unknown): Promise<BrowserExecutionErrorResult>;
|
|
123
117
|
watchActiveTestPage(page?: any): void;
|
|
124
118
|
stopTest(test: Test, meta?: Record<string, string>): Promise<void>;
|
|
125
119
|
unwatchActiveTestPages(): void;
|
|
126
|
-
hasPlaywrightLocator(locatorFn: (page: any) => any, opts?: {
|
|
127
|
-
multiple?: boolean;
|
|
128
|
-
contents?: boolean;
|
|
129
|
-
success?: (locator: any) => Promise<void> | void;
|
|
130
|
-
}): Promise<boolean>;
|
|
131
120
|
playwrightLocatorCount(locatorFn: (page: any) => any): Promise<number>;
|
|
132
121
|
bindFrameNavigated(page: any): void;
|
|
133
122
|
openFreshTab(): Promise<void>;
|
package/dist/src/explorer.js
CHANGED
|
@@ -12,15 +12,14 @@ import { visuallyAnnotateContainers } from "./ai/researcher/coordinates.js";
|
|
|
12
12
|
import { RequestStore } from "./api/request-store.js";
|
|
13
13
|
import { XhrCapture } from "./api/xhr-capture.js";
|
|
14
14
|
import { ConfigParser } from './config.js';
|
|
15
|
-
import { KnowledgeTracker } from './knowledge-tracker.js';
|
|
16
15
|
import { PlaywrightRecorder } from "./playwright-recorder.js";
|
|
17
16
|
import { Reporter } from "./reporter.js";
|
|
18
17
|
import { StateManager } from './state-manager.js';
|
|
19
18
|
import { TestResult } from "./test-plan.js";
|
|
20
|
-
import { BrowserRecoveryError, isFatalBrowserError, isNavigationTransitionError } from "./utils/browser-errors.js";
|
|
19
|
+
import { BrowserRecoveryError, browserErrorMessage, isFatalBrowserError, isNavigationTransitionError } from "./utils/browser-errors.js";
|
|
21
20
|
import { ELEMENT_EXTRACTION_CONFIG, getElementDataExtractorSource } from "./utils/html.js";
|
|
22
21
|
import { createDebug, log, tag } from './utils/logger.js';
|
|
23
|
-
import { waitForPageReadiness } from "./utils/page-readiness.js";
|
|
22
|
+
import { sleep, waitForPageReadiness } from "./utils/page-readiness.js";
|
|
24
23
|
import { WebElement } from "./utils/web-element.js";
|
|
25
24
|
const debugLog = createDebug('explorbot:explorer');
|
|
26
25
|
const RECOVERABLE_NAVIGATION_ERRORS = /net::ERR_ABORTED|page\.screenshot.*Timeout|waiting for fonts to load/i;
|
|
@@ -33,7 +32,6 @@ class Explorer {
|
|
|
33
32
|
stateManager;
|
|
34
33
|
knowledgeTracker;
|
|
35
34
|
config;
|
|
36
|
-
userResolveFn = null;
|
|
37
35
|
options;
|
|
38
36
|
reporter;
|
|
39
37
|
otherTabs = [];
|
|
@@ -45,21 +43,20 @@ class Explorer {
|
|
|
45
43
|
testPageErrorHandler = null;
|
|
46
44
|
testConsoleHandler = null;
|
|
47
45
|
testDialogHandler = null;
|
|
48
|
-
constructor(config, aiProvider, options) {
|
|
46
|
+
constructor(config, aiProvider, options, experienceTracker, knowledgeTracker) {
|
|
49
47
|
this.config = config;
|
|
50
48
|
this.aiProvider = aiProvider;
|
|
51
49
|
this.options = options;
|
|
52
50
|
this.initializeContainer();
|
|
53
|
-
this.
|
|
54
|
-
this.
|
|
51
|
+
this.knowledgeTracker = knowledgeTracker;
|
|
52
|
+
this.stateManager = new StateManager(experienceTracker, knowledgeTracker);
|
|
55
53
|
this.reporter = new Reporter(config.reporter, this.stateManager);
|
|
56
54
|
}
|
|
57
55
|
initializeContainer() {
|
|
58
56
|
try {
|
|
59
57
|
// Use project root for output directory, not current working directory
|
|
60
58
|
const configParser = ConfigParser.getInstance();
|
|
61
|
-
const
|
|
62
|
-
const projectRoot = configPath ? path.dirname(configPath) : process.cwd();
|
|
59
|
+
const projectRoot = configParser.getProjectRoot();
|
|
63
60
|
global.output_dir = path.join(projectRoot, 'output', 'states');
|
|
64
61
|
global.codecept_dir = projectRoot;
|
|
65
62
|
configParser.validateConfig(this.config);
|
|
@@ -113,31 +110,20 @@ class Explorer {
|
|
|
113
110
|
},
|
|
114
111
|
};
|
|
115
112
|
if (this.config.stepsFile) {
|
|
116
|
-
const
|
|
117
|
-
const projectRoot = configPath ? path.dirname(configPath) : process.cwd();
|
|
113
|
+
const projectRoot = ConfigParser.getInstance().getProjectRoot();
|
|
118
114
|
codeceptConfig.include = { I: path.resolve(projectRoot, this.config.stepsFile) };
|
|
119
115
|
}
|
|
120
116
|
return codeceptConfig;
|
|
121
117
|
}
|
|
122
118
|
getConfig() {
|
|
123
|
-
if (!this.config) {
|
|
124
|
-
throw new Error('Configuration not loaded. Call run() first.');
|
|
125
|
-
}
|
|
126
119
|
return this.config;
|
|
127
120
|
}
|
|
128
|
-
getConfigPath() {
|
|
129
|
-
const configParser = ConfigParser.getInstance();
|
|
130
|
-
return configParser.getConfigPath();
|
|
131
|
-
}
|
|
132
121
|
getAIProvider() {
|
|
133
122
|
return this.aiProvider;
|
|
134
123
|
}
|
|
135
124
|
getStateManager() {
|
|
136
125
|
return this.stateManager;
|
|
137
126
|
}
|
|
138
|
-
getCurrentUrl() {
|
|
139
|
-
return this.stateManager.getCurrentState().url || '?';
|
|
140
|
-
}
|
|
141
127
|
getKnowledgeTracker() {
|
|
142
128
|
return this.knowledgeTracker;
|
|
143
129
|
}
|
|
@@ -175,9 +161,6 @@ class Explorer {
|
|
|
175
161
|
if (this.isStarted) {
|
|
176
162
|
return;
|
|
177
163
|
}
|
|
178
|
-
if (!this.config) {
|
|
179
|
-
await this.initializeContainer();
|
|
180
|
-
}
|
|
181
164
|
await codeceptjs.recorder.start();
|
|
182
165
|
await codeceptjs.container.started(null);
|
|
183
166
|
storeListener();
|
|
@@ -294,8 +277,8 @@ class Explorer {
|
|
|
294
277
|
async executeAction(code) {
|
|
295
278
|
return this.runWithBrowserRecovery('executeAction', () => this.createAction().execute(code));
|
|
296
279
|
}
|
|
297
|
-
async attemptAction(code, originalMessage
|
|
298
|
-
return this.runWithBrowserRecovery('attemptAction', () => this.createAction().attempt(code, originalMessage
|
|
280
|
+
async attemptAction(code, originalMessage) {
|
|
281
|
+
return this.runWithBrowserRecovery('attemptAction', () => this.createAction().attempt(code, originalMessage));
|
|
299
282
|
}
|
|
300
283
|
getPlaywrightRecorder() {
|
|
301
284
|
return this.playwrightRecorder;
|
|
@@ -318,7 +301,7 @@ class Explorer {
|
|
|
318
301
|
await action.execute(`I.amOnPage(${serializedUrl})`);
|
|
319
302
|
}
|
|
320
303
|
catch (err) {
|
|
321
|
-
const msg =
|
|
304
|
+
const msg = browserErrorMessage(err);
|
|
322
305
|
if (!RECOVERABLE_NAVIGATION_ERRORS.test(msg))
|
|
323
306
|
throw err;
|
|
324
307
|
tag('warning').log(`Navigation warning (continuing after load): ${msg.split('\n')[0]}`);
|
|
@@ -363,7 +346,7 @@ class Explorer {
|
|
|
363
346
|
}
|
|
364
347
|
catch (error) {
|
|
365
348
|
if (this.isFatalBrowserError(error)) {
|
|
366
|
-
tag('warning').log(`getEidxInContainer: ${error
|
|
349
|
+
tag('warning').log(`getEidxInContainer: ${browserErrorMessage(error)}`);
|
|
367
350
|
await this.recoverFromBrowserError();
|
|
368
351
|
}
|
|
369
352
|
return [];
|
|
@@ -378,16 +361,12 @@ class Explorer {
|
|
|
378
361
|
}
|
|
379
362
|
catch (error) {
|
|
380
363
|
if (this.isFatalBrowserError(error)) {
|
|
381
|
-
tag('warning').log(`getEidxByLocator: ${error
|
|
364
|
+
tag('warning').log(`getEidxByLocator: ${browserErrorMessage(error)}`);
|
|
382
365
|
await this.recoverFromBrowserError();
|
|
383
366
|
}
|
|
384
367
|
return null;
|
|
385
368
|
}
|
|
386
369
|
}
|
|
387
|
-
async reload() {
|
|
388
|
-
await this.closeOtherTabs();
|
|
389
|
-
await this.playwrightHelper.page.reload();
|
|
390
|
-
}
|
|
391
370
|
resolveBrowserUrl(url) {
|
|
392
371
|
if (!url)
|
|
393
372
|
return null;
|
|
@@ -433,7 +412,7 @@ class Explorer {
|
|
|
433
412
|
return this.waitForPageReadiness();
|
|
434
413
|
}
|
|
435
414
|
catch (err) {
|
|
436
|
-
tag('error').log(`Browser recovery failed: ${err
|
|
415
|
+
tag('error').log(`Browser recovery failed: ${browserErrorMessage(err)}`);
|
|
437
416
|
return false;
|
|
438
417
|
}
|
|
439
418
|
}
|
|
@@ -471,7 +450,7 @@ class Explorer {
|
|
|
471
450
|
return true;
|
|
472
451
|
}
|
|
473
452
|
catch (err) {
|
|
474
|
-
tag('error').log(`Browser restart failed: ${err
|
|
453
|
+
tag('error').log(`Browser restart failed: ${browserErrorMessage(err)}`);
|
|
475
454
|
return false;
|
|
476
455
|
}
|
|
477
456
|
}
|
|
@@ -502,7 +481,7 @@ class Explorer {
|
|
|
502
481
|
}
|
|
503
482
|
catch (error) {
|
|
504
483
|
if (this.isFatalBrowserError(error)) {
|
|
505
|
-
tag('warning').log(`isInsideIframe: ${error
|
|
484
|
+
tag('warning').log(`isInsideIframe: ${browserErrorMessage(error)}`);
|
|
506
485
|
await this.recoverFromBrowserError();
|
|
507
486
|
}
|
|
508
487
|
return false;
|
|
@@ -522,9 +501,6 @@ class Explorer {
|
|
|
522
501
|
clearOtherTabsInfo() {
|
|
523
502
|
this.otherTabs = [];
|
|
524
503
|
}
|
|
525
|
-
setUserResolve(userResolveFn) {
|
|
526
|
-
this.userResolveFn = userResolveFn;
|
|
527
|
-
}
|
|
528
504
|
listenToStateChanged() {
|
|
529
505
|
if (!this.playwrightHelper) {
|
|
530
506
|
debugLog('Playwright helper not available for state monitoring');
|
|
@@ -658,11 +634,8 @@ class Explorer {
|
|
|
658
634
|
this.watchActiveTestPage();
|
|
659
635
|
return true;
|
|
660
636
|
}
|
|
661
|
-
async ensureActiveTestPageAvailable() {
|
|
662
|
-
return this.ensurePageAvailable();
|
|
663
|
-
}
|
|
664
637
|
async handleExecutionError(error) {
|
|
665
|
-
const message =
|
|
638
|
+
const message = browserErrorMessage(error);
|
|
666
639
|
tag('error').log(`Browser execution error: ${message}`);
|
|
667
640
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
668
641
|
return {
|
|
@@ -766,29 +739,6 @@ class Explorer {
|
|
|
766
739
|
}
|
|
767
740
|
this.observedTestPages.clear();
|
|
768
741
|
}
|
|
769
|
-
async hasPlaywrightLocator(locatorFn, opts = {}) {
|
|
770
|
-
try {
|
|
771
|
-
const pwLocator = locatorFn(this.playwrightHelper.page);
|
|
772
|
-
const count = await pwLocator.count();
|
|
773
|
-
if (opts.multiple ? count === 0 : count !== 1)
|
|
774
|
-
return false;
|
|
775
|
-
if (opts.contents) {
|
|
776
|
-
const html = await pwLocator.first().innerHTML();
|
|
777
|
-
if (!html?.trim())
|
|
778
|
-
return false;
|
|
779
|
-
}
|
|
780
|
-
if (opts.success)
|
|
781
|
-
await opts.success(pwLocator);
|
|
782
|
-
return true;
|
|
783
|
-
}
|
|
784
|
-
catch (error) {
|
|
785
|
-
if (this.isFatalBrowserError(error)) {
|
|
786
|
-
tag('warning').log(`hasPlaywrightLocator: ${error instanceof Error ? error.message : error}`);
|
|
787
|
-
await this.recoverFromBrowserError();
|
|
788
|
-
}
|
|
789
|
-
return false;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
742
|
async playwrightLocatorCount(locatorFn) {
|
|
793
743
|
try {
|
|
794
744
|
const pwLocator = locatorFn(this.playwrightHelper.page);
|
|
@@ -796,7 +746,7 @@ class Explorer {
|
|
|
796
746
|
}
|
|
797
747
|
catch (error) {
|
|
798
748
|
if (this.isFatalBrowserError(error)) {
|
|
799
|
-
tag('warning').log(`playwrightLocatorCount: ${error
|
|
749
|
+
tag('warning').log(`playwrightLocatorCount: ${browserErrorMessage(error)}`);
|
|
800
750
|
await this.recoverFromBrowserError();
|
|
801
751
|
}
|
|
802
752
|
throw error;
|
|
@@ -815,7 +765,7 @@ class Explorer {
|
|
|
815
765
|
debugLog('Failed to get page title:', error);
|
|
816
766
|
}
|
|
817
767
|
this.stateManager.updateStateFromBasic(newUrl, newTitle, 'navigation');
|
|
818
|
-
await
|
|
768
|
+
await sleep(500);
|
|
819
769
|
});
|
|
820
770
|
}
|
|
821
771
|
async openFreshTab() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ActionResult } from './action-result.js';
|
|
2
|
-
interface Knowledge {
|
|
2
|
+
export interface Knowledge {
|
|
3
3
|
filePath: string;
|
|
4
4
|
url: string;
|
|
5
5
|
content: string;
|
|
@@ -12,6 +12,7 @@ export declare class KnowledgeTracker {
|
|
|
12
12
|
constructor();
|
|
13
13
|
loadKnowledgeFiles(): void;
|
|
14
14
|
getRelevantKnowledge(state: ActionResult): Knowledge[];
|
|
15
|
+
renderRelevantKnowledge(state: ActionResult): string;
|
|
15
16
|
addKnowledge(urlPattern: string, description: string): {
|
|
16
17
|
filename: string;
|
|
17
18
|
filePath: string;
|
|
@@ -30,4 +31,3 @@ export declare class KnowledgeTracker {
|
|
|
30
31
|
normalizeUrl(url: string): string;
|
|
31
32
|
getStateParameters(state: ActionResult, keys: string[]): Record<string, any>;
|
|
32
33
|
}
|
|
33
|
-
export {};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import dedent from 'dedent';
|
|
3
4
|
import matter from 'gray-matter';
|
|
4
5
|
import { ActionResult } from './action-result.js';
|
|
5
6
|
import { ConfigParser } from './config.js';
|
|
6
7
|
import { getCliName } from "./utils/cli-name.js";
|
|
7
|
-
import { createDebug } from './utils/logger.js';
|
|
8
|
+
import { createDebug, pluralize, tag } from './utils/logger.js';
|
|
9
|
+
import { loadMarkdownFiles } from './utils/markdown-files.js';
|
|
10
|
+
import { mdq } from './utils/markdown-query.js';
|
|
8
11
|
import { isSecretName, registerSecret } from './utils/secrets.js';
|
|
12
|
+
import { slugify } from './utils/strings.js';
|
|
9
13
|
const debugLog = createDebug('explorbot:knowledge-tracker');
|
|
10
14
|
export class KnowledgeTracker {
|
|
11
15
|
knowledgeDir;
|
|
@@ -14,14 +18,7 @@ export class KnowledgeTracker {
|
|
|
14
18
|
constructor() {
|
|
15
19
|
const configParser = ConfigParser.getInstance();
|
|
16
20
|
const config = configParser.getConfig();
|
|
17
|
-
|
|
18
|
-
if (configPath) {
|
|
19
|
-
const projectRoot = dirname(configPath);
|
|
20
|
-
this.knowledgeDir = join(projectRoot, config.dirs?.knowledge || 'knowledge');
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
this.knowledgeDir = config.dirs?.knowledge || 'knowledge';
|
|
24
|
-
}
|
|
21
|
+
this.knowledgeDir = configParser.resolveProjectDir(config.dirs?.knowledge || 'knowledge');
|
|
25
22
|
if (!existsSync(this.knowledgeDir)) {
|
|
26
23
|
mkdirSync(this.knowledgeDir, { recursive: true });
|
|
27
24
|
}
|
|
@@ -30,27 +27,13 @@ export class KnowledgeTracker {
|
|
|
30
27
|
if (this.isLoaded)
|
|
31
28
|
return;
|
|
32
29
|
this.knowledgeFiles = [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const fileContent = readFileSync(filePath, 'utf8');
|
|
42
|
-
const parsed = matter(fileContent);
|
|
43
|
-
const urlPattern = parsed.data.url || parsed.data.path || '*';
|
|
44
|
-
this.knowledgeFiles.push({
|
|
45
|
-
filePath,
|
|
46
|
-
url: urlPattern,
|
|
47
|
-
content: this.interpolateVars(parsed.content),
|
|
48
|
-
...parsed.data,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
// Skip invalid files
|
|
53
|
-
}
|
|
30
|
+
for (const entry of loadMarkdownFiles(this.knowledgeDir, { recursive: true })) {
|
|
31
|
+
this.knowledgeFiles.push({
|
|
32
|
+
filePath: entry.filePath,
|
|
33
|
+
url: entry.data.url || entry.data.path || '*',
|
|
34
|
+
content: this.interpolateVars(entry.content),
|
|
35
|
+
...entry.data,
|
|
36
|
+
});
|
|
54
37
|
}
|
|
55
38
|
this.isLoaded = true;
|
|
56
39
|
}
|
|
@@ -60,21 +43,35 @@ export class KnowledgeTracker {
|
|
|
60
43
|
return state.isMatchedBy(knowledge);
|
|
61
44
|
});
|
|
62
45
|
}
|
|
46
|
+
renderRelevantKnowledge(state) {
|
|
47
|
+
const knowledgeFiles = this.getRelevantKnowledge(state);
|
|
48
|
+
if (knowledgeFiles.length === 0)
|
|
49
|
+
return '';
|
|
50
|
+
const knowledgeContent = knowledgeFiles
|
|
51
|
+
.map((k) => k.content)
|
|
52
|
+
.filter((k) => !!k)
|
|
53
|
+
.join('\n\n');
|
|
54
|
+
tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
|
|
55
|
+
return dedent `
|
|
56
|
+
<knowledge>
|
|
57
|
+
Here is relevant knowledge for this page:
|
|
58
|
+
|
|
59
|
+
${knowledgeContent}
|
|
60
|
+
</knowledge>
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
63
|
addKnowledge(urlPattern, description) {
|
|
64
64
|
const configParser = ConfigParser.getInstance();
|
|
65
|
-
const config = configParser.getConfig();
|
|
66
65
|
const configPath = configParser.getConfigPath();
|
|
67
66
|
if (!configPath) {
|
|
68
67
|
throw new Error(`No explorbot configuration found. Please run "${getCliName()} init" first.`);
|
|
69
68
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!existsSync(knowledgeDir)) {
|
|
73
|
-
mkdirSync(knowledgeDir, { recursive: true });
|
|
69
|
+
if (!existsSync(this.knowledgeDir)) {
|
|
70
|
+
mkdirSync(this.knowledgeDir, { recursive: true });
|
|
74
71
|
}
|
|
75
72
|
const normalizedUrl = this.normalizeUrl(urlPattern);
|
|
76
73
|
const filename = this.generateFilename(normalizedUrl);
|
|
77
|
-
const filePath = join(knowledgeDir, filename);
|
|
74
|
+
const filePath = join(this.knowledgeDir, filename);
|
|
78
75
|
const isNewFile = !existsSync(filePath);
|
|
79
76
|
if (isNewFile) {
|
|
80
77
|
const frontmatter = {
|
|
@@ -101,6 +98,7 @@ export class KnowledgeTracker {
|
|
|
101
98
|
const fileContent = matter.stringify(newContent, frontmatter);
|
|
102
99
|
writeFileSync(filePath, fileContent, 'utf8');
|
|
103
100
|
}
|
|
101
|
+
this.isLoaded = false;
|
|
104
102
|
return { filename, filePath, isNewFile };
|
|
105
103
|
}
|
|
106
104
|
interpolateVars(content) {
|
|
@@ -129,12 +127,7 @@ export class KnowledgeTracker {
|
|
|
129
127
|
});
|
|
130
128
|
}
|
|
131
129
|
generateFilename(url) {
|
|
132
|
-
let filename = url
|
|
133
|
-
.replace(/https?:\/\//g, '')
|
|
134
|
-
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
135
|
-
.replace(/_+/g, '_')
|
|
136
|
-
.replace(/^_|_$/g, '')
|
|
137
|
-
.toLowerCase();
|
|
130
|
+
let filename = slugify(url.replace(/https?:\/\//g, ''));
|
|
138
131
|
if (!filename || filename === '*') {
|
|
139
132
|
filename = 'general';
|
|
140
133
|
}
|
|
@@ -156,7 +149,7 @@ export class KnowledgeTracker {
|
|
|
156
149
|
this.loadKnowledgeFiles();
|
|
157
150
|
return this.knowledgeFiles.map((knowledge) => {
|
|
158
151
|
const content = knowledge.content.trim();
|
|
159
|
-
const firstLine = content.split('\n')[0]?.trim() || '';
|
|
152
|
+
const firstLine = mdq(content).meta()[0]?.text.split('\n')[0]?.trim() || '';
|
|
160
153
|
return {
|
|
161
154
|
url: knowledge.url,
|
|
162
155
|
firstLine,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ActionResult } from './action-result.js';
|
|
2
|
-
import { ExperienceTracker } from './experience-tracker.js';
|
|
2
|
+
import type { ExperienceTracker } from './experience-tracker.js';
|
|
3
|
+
import type { KnowledgeTracker, Knowledge } from './knowledge-tracker.js';
|
|
3
4
|
export interface Link {
|
|
4
5
|
title: string;
|
|
5
6
|
url: string;
|
|
@@ -54,24 +55,18 @@ export interface StateTransition {
|
|
|
54
55
|
trigger: 'manual' | 'navigation' | 'automatic';
|
|
55
56
|
}
|
|
56
57
|
export type StateChangeListener = (event: StateTransition) => void;
|
|
57
|
-
export
|
|
58
|
-
/** File path */
|
|
59
|
-
filePath: string;
|
|
60
|
-
/** Markdown content */
|
|
61
|
-
content: string;
|
|
62
|
-
}
|
|
58
|
+
export type { Knowledge };
|
|
63
59
|
export declare class StateManager {
|
|
64
60
|
currentState: WebPageState | null;
|
|
65
61
|
stateHistory: StateTransition[];
|
|
66
62
|
allVisitedUrls: Set<string>;
|
|
67
|
-
knowledgeCache: Knowledge[];
|
|
68
|
-
lastKnowledgeScan: Date | null;
|
|
69
63
|
stateChangeListeners: StateChangeListener[];
|
|
70
64
|
experienceTracker: ExperienceTracker;
|
|
71
|
-
|
|
65
|
+
knowledgeTracker: KnowledgeTracker;
|
|
72
66
|
nextStateId: number;
|
|
73
|
-
constructor();
|
|
67
|
+
constructor(experienceTracker: ExperienceTracker, knowledgeTracker: KnowledgeTracker);
|
|
74
68
|
getExperienceTracker(): ExperienceTracker;
|
|
69
|
+
getKnowledgeTracker(): KnowledgeTracker;
|
|
75
70
|
/**
|
|
76
71
|
* Subscribe to state change events
|
|
77
72
|
*/
|
|
@@ -80,11 +75,6 @@ export declare class StateManager {
|
|
|
80
75
|
* Emit state change event to all listeners
|
|
81
76
|
*/
|
|
82
77
|
emitStateChange(event: StateTransition): void;
|
|
83
|
-
/**
|
|
84
|
-
* Extract state path from full URL
|
|
85
|
-
* Removes domain, port, protocol
|
|
86
|
-
* Keeps path, query, and hash: /path/to/page?tab=users#section
|
|
87
|
-
*/
|
|
88
78
|
/**
|
|
89
79
|
* Update current state from ActionResult and record transition if state changed
|
|
90
80
|
*/
|
|
@@ -119,10 +109,6 @@ export declare class StateManager {
|
|
|
119
109
|
* Get the last N transitions
|
|
120
110
|
*/
|
|
121
111
|
getRecentTransitions(count?: number): StateTransition[];
|
|
122
|
-
/**
|
|
123
|
-
* Scan knowledge directory for .md files and cache them
|
|
124
|
-
*/
|
|
125
|
-
scanKnowledgeFiles(): void;
|
|
126
112
|
/**
|
|
127
113
|
* Get relevant knowledge files for current state
|
|
128
114
|
*/
|
|
@@ -134,12 +120,6 @@ export declare class StateManager {
|
|
|
134
120
|
/**
|
|
135
121
|
* Get all context for current state (knowledge + experience)
|
|
136
122
|
*/
|
|
137
|
-
getCurrentContext(): {
|
|
138
|
-
state: WebPageState;
|
|
139
|
-
knowledge: Knowledge[];
|
|
140
|
-
experience: string[];
|
|
141
|
-
recentTransitions: StateTransition[];
|
|
142
|
-
};
|
|
143
123
|
/**
|
|
144
124
|
* Check if we've been in this state before
|
|
145
125
|
*/
|
|
@@ -158,11 +138,6 @@ export declare class StateManager {
|
|
|
158
138
|
* If the last transition changed URL, returns the fromState (for URL change detection).
|
|
159
139
|
* Otherwise returns the most recent toState with content (for diffing).
|
|
160
140
|
*/
|
|
161
|
-
getPreviousState(): WebPageState | null;
|
|
162
|
-
/**
|
|
163
|
-
* Load HTML content from file
|
|
164
|
-
*/
|
|
165
|
-
loadHtmlFromFile(htmlFile: string): string | null;
|
|
166
141
|
/**
|
|
167
142
|
* Clear state history (useful for testing or reset)
|
|
168
143
|
* Note: This preserves the current state, only clears navigation history
|