explorbot 0.1.32-beta.1 → 0.2.1
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 +35 -19
- package/bin/explorbot-cli.ts +33 -10
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +26 -28
- package/boat/doc-collector/src/ai/tools.ts +74 -16
- package/boat/doc-collector/src/cli.ts +1 -6
- package/boat/doc-collector/src/docbot.ts +30 -9
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +34 -2
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +29 -9
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +19 -7
- package/dist/boat/doc-collector/src/ai/tools.js +53 -16
- package/dist/boat/doc-collector/src/cli.js +1 -5
- package/dist/boat/doc-collector/src/docbot.js +27 -8
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +30 -1
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/models.json +30 -0
- package/dist/package.json +23 -3
- package/dist/src/action-result.d.ts +8 -10
- package/dist/src/action-result.js +72 -164
- package/dist/src/action.d.ts +13 -12
- package/dist/src/action.js +37 -113
- package/dist/src/ai/agent.d.ts +17 -0
- package/dist/src/ai/captain/idle-mode.js +1 -1
- package/dist/src/ai/captain/test-mode.js +1 -1
- package/dist/src/ai/captain/web-mode.js +18 -29
- package/dist/src/ai/captain.d.ts +1 -3
- package/dist/src/ai/captain.js +17 -45
- package/dist/src/ai/conversation.d.ts +2 -5
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +2 -17
- package/dist/src/ai/driller.js +26 -50
- 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 +4 -3
- package/dist/src/ai/historian/playwright.d.ts +4 -1
- package/dist/src/ai/historian/playwright.js +4 -3
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -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 +10 -8
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +8 -6
- package/dist/src/ai/navigator.js +34 -65
- package/dist/src/ai/pilot.d.ts +13 -8
- package/dist/src/ai/pilot.js +53 -59
- package/dist/src/ai/planner.d.ts +5 -4
- package/dist/src/ai/planner.js +66 -52
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +77 -137
- package/dist/src/ai/quartermaster.d.ts +22 -22
- package/dist/src/ai/quartermaster.js +8 -14
- package/dist/src/ai/rerunner.d.ts +3 -13
- package/dist/src/ai/rerunner.js +23 -44
- package/dist/src/ai/researcher/cache.js +11 -18
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
- package/dist/src/ai/researcher/deep-analysis.js +8 -11
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- 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.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +7 -3
- package/dist/src/ai/researcher.js +40 -106
- 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 +19 -6
- package/dist/src/ai/task-agent.js +37 -33
- package/dist/src/ai/tester.d.ts +6 -14
- package/dist/src/ai/tester.js +46 -111
- package/dist/src/ai/tools.d.ts +26 -14
- package/dist/src/ai/tools.js +138 -132
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/compact-command.d.ts +1 -1
- package/dist/src/commands/compact-command.js +6 -6
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +8 -6
- package/dist/src/commands/context-data-command.js +2 -2
- package/dist/src/commands/context-experience-command.js +2 -2
- package/dist/src/commands/context-html-command.js +2 -2
- package/dist/src/commands/context-knowledge-command.js +2 -2
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/experience-command.js +1 -1
- package/dist/src/commands/explore-command.js +4 -3
- package/dist/src/commands/freesail-command.js +2 -2
- package/dist/src/commands/knows-command.js +1 -1
- package/dist/src/commands/learn-command.js +5 -3
- package/dist/src/commands/path-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/test-command.js +1 -1
- package/dist/src/components/AddKnowledge.d.ts +2 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/App.js +3 -3
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.js +156 -10
- package/dist/src/experience-tracker.d.ts +11 -6
- package/dist/src/experience-tracker.js +82 -155
- package/dist/src/explorbot.d.ts +21 -13
- package/dist/src/explorbot.js +84 -80
- package/dist/src/explorer.d.ts +66 -102
- package/dist/src/explorer.js +332 -623
- package/dist/src/knowledge-tracker.d.ts +2 -2
- package/dist/src/knowledge-tracker.js +38 -45
- package/dist/src/state-manager.d.ts +10 -30
- package/dist/src/state-manager.js +11 -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/hooks-runner.js +2 -4
- 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/test-files.js +1 -1
- package/dist/src/utils/web-annotate.d.ts +5 -0
- package/dist/src/utils/web-annotate.js +58 -0
- package/dist/src/utils/web-eidx.d.ts +2 -0
- package/dist/src/utils/web-eidx.js +20 -0
- package/dist/src/utils/web-element.d.ts +3 -1
- package/dist/src/utils/web-element.js +32 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/models.json +30 -0
- package/package.json +23 -3
- package/src/action-result.ts +70 -173
- package/src/action.ts +38 -132
- package/src/ai/agent.ts +20 -0
- package/src/ai/captain/idle-mode.ts +1 -1
- package/src/ai/captain/test-mode.ts +1 -1
- package/src/ai/captain/web-mode.ts +18 -30
- package/src/ai/captain.ts +17 -42
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +53 -83
- 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 +5 -3
- package/src/ai/historian/playwright.ts +7 -4
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +6 -8
- package/src/ai/navigator.ts +39 -70
- package/src/ai/pilot.ts +59 -61
- package/src/ai/planner.ts +76 -62
- package/src/ai/provider.ts +85 -146
- package/src/ai/quartermaster.ts +39 -45
- package/src/ai/rerunner.ts +27 -50
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +10 -11
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +9 -3
- package/src/ai/researcher.ts +42 -119
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +45 -38
- package/src/ai/tester.ts +51 -129
- package/src/ai/tools.ts +145 -153
- package/src/command-handler.ts +1 -1
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +8 -6
- package/src/commands/context-data-command.ts +2 -2
- package/src/commands/context-experience-command.ts +2 -2
- package/src/commands/context-html-command.ts +2 -2
- package/src/commands/context-knowledge-command.ts +2 -2
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +5 -3
- package/src/commands/path-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/test-command.ts +1 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +3 -3
- package/src/config.ts +185 -11
- package/src/experience-tracker.ts +89 -152
- package/src/explorbot.ts +88 -83
- package/src/explorer.ts +402 -693
- package/src/knowledge-tracker.ts +42 -49
- package/src/state-manager.ts +20 -155
- 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/hooks-runner.ts +2 -4
- 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/test-files.ts +1 -1
- package/src/utils/web-annotate.ts +64 -0
- package/src/utils/web-eidx.ts +21 -0
- package/src/utils/web-element.ts +34 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/dist/boat/api-tester/example/apibot.config.js +0 -30
package/src/knowledge-tracker.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
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.ts';
|
|
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
|
|
|
10
14
|
const debugLog = createDebug('explorbot:knowledge-tracker');
|
|
11
15
|
|
|
12
|
-
interface Knowledge {
|
|
16
|
+
export interface Knowledge {
|
|
13
17
|
filePath: string;
|
|
14
18
|
url: string;
|
|
15
19
|
content: string;
|
|
@@ -24,14 +28,7 @@ export class KnowledgeTracker {
|
|
|
24
28
|
constructor() {
|
|
25
29
|
const configParser = ConfigParser.getInstance();
|
|
26
30
|
const config = configParser.getConfig();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (configPath) {
|
|
30
|
-
const projectRoot = dirname(configPath);
|
|
31
|
-
this.knowledgeDir = join(projectRoot, config.dirs?.knowledge || 'knowledge');
|
|
32
|
-
} else {
|
|
33
|
-
this.knowledgeDir = config.dirs?.knowledge || 'knowledge';
|
|
34
|
-
}
|
|
31
|
+
this.knowledgeDir = configParser.resolveProjectDir(config.dirs?.knowledge || 'knowledge');
|
|
35
32
|
|
|
36
33
|
if (!existsSync(this.knowledgeDir)) {
|
|
37
34
|
mkdirSync(this.knowledgeDir, { recursive: true });
|
|
@@ -43,29 +40,13 @@ export class KnowledgeTracker {
|
|
|
43
40
|
|
|
44
41
|
this.knowledgeFiles = [];
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
for (const filePath of files) {
|
|
55
|
-
try {
|
|
56
|
-
const fileContent = readFileSync(filePath, 'utf8');
|
|
57
|
-
const parsed = matter(fileContent);
|
|
58
|
-
const urlPattern = parsed.data.url || parsed.data.path || '*';
|
|
59
|
-
|
|
60
|
-
this.knowledgeFiles.push({
|
|
61
|
-
filePath,
|
|
62
|
-
url: urlPattern,
|
|
63
|
-
content: this.interpolateVars(parsed.content),
|
|
64
|
-
...parsed.data,
|
|
65
|
-
});
|
|
66
|
-
} catch (error) {
|
|
67
|
-
// Skip invalid files
|
|
68
|
-
}
|
|
43
|
+
for (const entry of loadMarkdownFiles(this.knowledgeDir, { recursive: true })) {
|
|
44
|
+
this.knowledgeFiles.push({
|
|
45
|
+
filePath: entry.filePath,
|
|
46
|
+
url: entry.data.url || entry.data.path || '*',
|
|
47
|
+
content: this.interpolateVars(entry.content),
|
|
48
|
+
...entry.data,
|
|
49
|
+
});
|
|
69
50
|
}
|
|
70
51
|
|
|
71
52
|
this.isLoaded = true;
|
|
@@ -79,25 +60,40 @@ export class KnowledgeTracker {
|
|
|
79
60
|
});
|
|
80
61
|
}
|
|
81
62
|
|
|
63
|
+
renderRelevantKnowledge(state: ActionResult): string {
|
|
64
|
+
const knowledgeFiles = this.getRelevantKnowledge(state);
|
|
65
|
+
if (knowledgeFiles.length === 0) return '';
|
|
66
|
+
|
|
67
|
+
const knowledgeContent = knowledgeFiles
|
|
68
|
+
.map((k) => k.content)
|
|
69
|
+
.filter((k) => !!k)
|
|
70
|
+
.join('\n\n');
|
|
71
|
+
|
|
72
|
+
tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
|
|
73
|
+
return dedent`
|
|
74
|
+
<knowledge>
|
|
75
|
+
Here is relevant knowledge for this page:
|
|
76
|
+
|
|
77
|
+
${knowledgeContent}
|
|
78
|
+
</knowledge>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
82
|
addKnowledge(urlPattern: string, description: string): { filename: string; filePath: string; isNewFile: boolean } {
|
|
83
83
|
const configParser = ConfigParser.getInstance();
|
|
84
|
-
const config = configParser.getConfig();
|
|
85
84
|
const configPath = configParser.getConfigPath();
|
|
86
85
|
|
|
87
86
|
if (!configPath) {
|
|
88
87
|
throw new Error(`No explorbot configuration found. Please run "${getCliName()} init" first.`);
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (!existsSync(knowledgeDir)) {
|
|
95
|
-
mkdirSync(knowledgeDir, { recursive: true });
|
|
90
|
+
if (!existsSync(this.knowledgeDir)) {
|
|
91
|
+
mkdirSync(this.knowledgeDir, { recursive: true });
|
|
96
92
|
}
|
|
97
93
|
|
|
98
94
|
const normalizedUrl = this.normalizeUrl(urlPattern);
|
|
99
95
|
const filename = this.generateFilename(normalizedUrl);
|
|
100
|
-
const filePath = join(knowledgeDir, filename);
|
|
96
|
+
const filePath = join(this.knowledgeDir, filename);
|
|
101
97
|
|
|
102
98
|
const isNewFile = !existsSync(filePath);
|
|
103
99
|
|
|
@@ -128,6 +124,8 @@ export class KnowledgeTracker {
|
|
|
128
124
|
writeFileSync(filePath, fileContent, 'utf8');
|
|
129
125
|
}
|
|
130
126
|
|
|
127
|
+
this.isLoaded = false;
|
|
128
|
+
|
|
131
129
|
return { filename, filePath, isNewFile };
|
|
132
130
|
}
|
|
133
131
|
|
|
@@ -158,12 +156,7 @@ export class KnowledgeTracker {
|
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
private generateFilename(url: string): string {
|
|
161
|
-
let filename = url
|
|
162
|
-
.replace(/https?:\/\//g, '')
|
|
163
|
-
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
164
|
-
.replace(/_+/g, '_')
|
|
165
|
-
.replace(/^_|_$/g, '')
|
|
166
|
-
.toLowerCase();
|
|
159
|
+
let filename = slugify(url.replace(/https?:\/\//g, ''));
|
|
167
160
|
|
|
168
161
|
if (!filename || filename === '*') {
|
|
169
162
|
filename = 'general';
|
|
@@ -194,7 +187,7 @@ export class KnowledgeTracker {
|
|
|
194
187
|
|
|
195
188
|
return this.knowledgeFiles.map((knowledge) => {
|
|
196
189
|
const content = knowledge.content.trim();
|
|
197
|
-
const firstLine = content.split('\n')[0]?.trim() || '';
|
|
190
|
+
const firstLine = mdq(content).meta()[0]?.text.split('\n')[0]?.trim() || '';
|
|
198
191
|
return {
|
|
199
192
|
url: knowledge.url,
|
|
200
193
|
firstLine,
|
package/src/state-manager.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import matter from 'gray-matter';
|
|
4
1
|
import { ActionResult } from './action-result.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import type { ExperienceTracker } from './experience-tracker.js';
|
|
3
|
+
import type { Knowledge, KnowledgeTracker } from './knowledge-tracker.js';
|
|
7
4
|
import { detectFocusArea } from './utils/aria.js';
|
|
8
|
-
import { createDebug
|
|
5
|
+
import { createDebug } from './utils/logger.js';
|
|
6
|
+
import { slugify } from './utils/strings.js';
|
|
9
7
|
import { extractStatePath } from './utils/url-matcher.js';
|
|
10
8
|
|
|
11
9
|
const debugLog = createDebug('explorbot:state');
|
|
@@ -69,43 +67,36 @@ export interface StateTransition {
|
|
|
69
67
|
|
|
70
68
|
export type StateChangeListener = (event: StateTransition) => void;
|
|
71
69
|
|
|
72
|
-
export interface
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
/** Markdown content */
|
|
76
|
-
content: string;
|
|
70
|
+
export interface TabInfo {
|
|
71
|
+
url: string;
|
|
72
|
+
title: string;
|
|
77
73
|
}
|
|
78
74
|
|
|
75
|
+
export type { Knowledge };
|
|
76
|
+
|
|
79
77
|
export class StateManager {
|
|
78
|
+
otherTabs: TabInfo[] = [];
|
|
80
79
|
private currentState: WebPageState | null = null;
|
|
81
80
|
private stateHistory: StateTransition[] = [];
|
|
82
81
|
private allVisitedUrls: Set<string> = new Set();
|
|
83
|
-
private knowledgeCache: Knowledge[] = [];
|
|
84
|
-
private lastKnowledgeScan: Date | null = null;
|
|
85
82
|
private stateChangeListeners: StateChangeListener[] = [];
|
|
86
83
|
private experienceTracker!: ExperienceTracker;
|
|
87
|
-
private
|
|
84
|
+
private knowledgeTracker: KnowledgeTracker;
|
|
88
85
|
private nextStateId = 1;
|
|
89
86
|
|
|
90
|
-
constructor() {
|
|
91
|
-
this.experienceTracker =
|
|
92
|
-
|
|
93
|
-
const config = configParser.getConfig();
|
|
94
|
-
const configPath = configParser.getConfigPath();
|
|
95
|
-
|
|
96
|
-
// Resolve knowledge directory relative to the config file location (project root)
|
|
97
|
-
if (configPath) {
|
|
98
|
-
const projectRoot = dirname(configPath);
|
|
99
|
-
this.knowledgeDir = join(projectRoot, config.dirs?.knowledge || 'knowledge');
|
|
100
|
-
} else {
|
|
101
|
-
this.knowledgeDir = config.dirs?.knowledge || 'knowledge';
|
|
102
|
-
}
|
|
87
|
+
constructor(experienceTracker: ExperienceTracker, knowledgeTracker: KnowledgeTracker) {
|
|
88
|
+
this.experienceTracker = experienceTracker;
|
|
89
|
+
this.knowledgeTracker = knowledgeTracker;
|
|
103
90
|
}
|
|
104
91
|
|
|
105
92
|
getExperienceTracker(): ExperienceTracker {
|
|
106
93
|
return this.experienceTracker;
|
|
107
94
|
}
|
|
108
95
|
|
|
96
|
+
getKnowledgeTracker(): KnowledgeTracker {
|
|
97
|
+
return this.knowledgeTracker;
|
|
98
|
+
}
|
|
99
|
+
|
|
109
100
|
/**
|
|
110
101
|
* Subscribe to state change events
|
|
111
102
|
*/
|
|
@@ -134,11 +125,6 @@ export class StateManager {
|
|
|
134
125
|
});
|
|
135
126
|
}
|
|
136
127
|
|
|
137
|
-
/**
|
|
138
|
-
* Extract state path from full URL
|
|
139
|
-
* Removes domain, port, protocol
|
|
140
|
-
* Keeps path, query, and hash: /path/to/page?tab=users#section
|
|
141
|
-
*/
|
|
142
128
|
/**
|
|
143
129
|
* Update current state from ActionResult and record transition if state changed
|
|
144
130
|
*/
|
|
@@ -230,13 +216,7 @@ export class StateManager {
|
|
|
230
216
|
parts.push(`title_${title}`);
|
|
231
217
|
}
|
|
232
218
|
|
|
233
|
-
return parts
|
|
234
|
-
.join('_')
|
|
235
|
-
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
236
|
-
.replace(/_+/g, '_')
|
|
237
|
-
.replace(/^_|_$/g, '')
|
|
238
|
-
.toLowerCase()
|
|
239
|
-
.substring(0, 200);
|
|
219
|
+
return slugify(parts.join('_')).substring(0, 200);
|
|
240
220
|
}
|
|
241
221
|
|
|
242
222
|
/**
|
|
@@ -327,65 +307,14 @@ export class StateManager {
|
|
|
327
307
|
return this.stateHistory.slice(-count);
|
|
328
308
|
}
|
|
329
309
|
|
|
330
|
-
/**
|
|
331
|
-
* Scan knowledge directory for .md files and cache them
|
|
332
|
-
*/
|
|
333
|
-
private scanKnowledgeFiles(): void {
|
|
334
|
-
const now = new Date();
|
|
335
|
-
|
|
336
|
-
// Only rescan every 30 seconds to avoid excessive file I/O
|
|
337
|
-
if (this.lastKnowledgeScan && now.getTime() - this.lastKnowledgeScan.getTime() < 30000) {
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
this.knowledgeCache = [];
|
|
342
|
-
|
|
343
|
-
if (!existsSync(this.knowledgeDir)) {
|
|
344
|
-
debugLog(`Knowledge directory not found: ${this.knowledgeDir}`);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
const files = readdirSync(this.knowledgeDir, { recursive: true })
|
|
350
|
-
.filter((file) => typeof file === 'string' && file.endsWith('.md'))
|
|
351
|
-
.map((file) => join(this.knowledgeDir, file as string));
|
|
352
|
-
|
|
353
|
-
for (const filePath of files) {
|
|
354
|
-
try {
|
|
355
|
-
const fileContent = readFileSync(filePath, 'utf8');
|
|
356
|
-
const parsed = matter(fileContent);
|
|
357
|
-
|
|
358
|
-
const urlPattern = parsed.data.url || parsed.data.path || '*';
|
|
359
|
-
|
|
360
|
-
this.knowledgeCache.push({
|
|
361
|
-
filePath,
|
|
362
|
-
url: urlPattern,
|
|
363
|
-
...parsed.data,
|
|
364
|
-
content: parsed.content,
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
debugLog(`Loaded knowledge file: ${filePath} (pattern: ${urlPattern})`);
|
|
368
|
-
} catch (error) {
|
|
369
|
-
debugLog(`Failed to load knowledge file ${filePath}:`, error);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
this.lastKnowledgeScan = now;
|
|
374
|
-
debugLog(`Scanned ${this.knowledgeCache.length} knowledge files`);
|
|
375
|
-
} catch (error) {
|
|
376
|
-
debugLog('Failed to scan knowledge directory:', error);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
310
|
/**
|
|
380
311
|
* Get relevant knowledge files for current state
|
|
381
312
|
*/
|
|
382
313
|
getRelevantKnowledge(): Knowledge[] {
|
|
383
314
|
if (!this.currentState) return [];
|
|
384
315
|
|
|
385
|
-
this.scanKnowledgeFiles();
|
|
386
|
-
|
|
387
316
|
const actionResult = ActionResult.fromState(this.currentState);
|
|
388
|
-
return this.
|
|
317
|
+
return this.knowledgeTracker.getRelevantKnowledge(actionResult);
|
|
389
318
|
}
|
|
390
319
|
|
|
391
320
|
/**
|
|
@@ -402,24 +331,6 @@ export class StateManager {
|
|
|
402
331
|
/**
|
|
403
332
|
* Get all context for current state (knowledge + experience)
|
|
404
333
|
*/
|
|
405
|
-
getCurrentContext(): {
|
|
406
|
-
state: WebPageState;
|
|
407
|
-
knowledge: Knowledge[];
|
|
408
|
-
experience: string[];
|
|
409
|
-
recentTransitions: StateTransition[];
|
|
410
|
-
} {
|
|
411
|
-
if (!this.currentState) {
|
|
412
|
-
throw new Error('No current state available');
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
return {
|
|
416
|
-
state: this.currentState,
|
|
417
|
-
knowledge: this.getRelevantKnowledge(),
|
|
418
|
-
experience: this.getRelevantExperience(),
|
|
419
|
-
recentTransitions: this.getRecentTransitions(),
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
334
|
/**
|
|
424
335
|
* Check if we've been in this state before
|
|
425
336
|
*/
|
|
@@ -455,45 +366,6 @@ export class StateManager {
|
|
|
455
366
|
* If the last transition changed URL, returns the fromState (for URL change detection).
|
|
456
367
|
* Otherwise returns the most recent toState with content (for diffing).
|
|
457
368
|
*/
|
|
458
|
-
getPreviousState(): WebPageState | null {
|
|
459
|
-
if (this.stateHistory.length === 0) return null;
|
|
460
|
-
|
|
461
|
-
const lastTransition = this.stateHistory[this.stateHistory.length - 1];
|
|
462
|
-
|
|
463
|
-
if (lastTransition.fromState?.url !== lastTransition.toState?.url) {
|
|
464
|
-
return lastTransition.fromState;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
for (let i = this.stateHistory.length - 1; i >= 0; i--) {
|
|
468
|
-
const toState = this.stateHistory[i].toState;
|
|
469
|
-
|
|
470
|
-
if (!toState) continue;
|
|
471
|
-
if (toState.id === this.currentState?.id) continue;
|
|
472
|
-
|
|
473
|
-
if (toState.html || toState.ariaSnapshot) {
|
|
474
|
-
return toState;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return null;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Load HTML content from file
|
|
483
|
-
*/
|
|
484
|
-
loadHtmlFromFile(htmlFile: string): string | null {
|
|
485
|
-
try {
|
|
486
|
-
const filePath = outputPath('states', htmlFile);
|
|
487
|
-
if (existsSync(filePath)) {
|
|
488
|
-
return readFileSync(filePath, 'utf8');
|
|
489
|
-
}
|
|
490
|
-
return null;
|
|
491
|
-
} catch (error) {
|
|
492
|
-
debugLog('Failed to load HTML from file:', error);
|
|
493
|
-
return null;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
369
|
/**
|
|
498
370
|
* Clear state history (useful for testing or reset)
|
|
499
371
|
* Note: This preserves the current state, only clears navigation history
|
|
@@ -527,15 +399,8 @@ export class StateManager {
|
|
|
527
399
|
this.stateHistory = [];
|
|
528
400
|
this.allVisitedUrls.clear();
|
|
529
401
|
this.stateChangeListeners = [];
|
|
530
|
-
this.knowledgeCache = [];
|
|
531
|
-
this.lastKnowledgeScan = null;
|
|
532
402
|
this.nextStateId = 1;
|
|
533
403
|
|
|
534
|
-
// Clean up experience tracker if it has cleanup method
|
|
535
|
-
if (this.experienceTracker && typeof this.experienceTracker.cleanup === 'function') {
|
|
536
|
-
this.experienceTracker.cleanup();
|
|
537
|
-
}
|
|
538
|
-
|
|
539
404
|
debugLog('StateManager cleanup completed');
|
|
540
405
|
}
|
|
541
406
|
}
|
package/src/utils/aria.ts
CHANGED
|
@@ -483,7 +483,7 @@ export const compactAriaSnapshot = (snapshot: string | null, keepNamed = false):
|
|
|
483
483
|
return renderTree(tree);
|
|
484
484
|
};
|
|
485
485
|
|
|
486
|
-
export const diffAriaSnapshots = (previous: string | null, current: string | null):
|
|
486
|
+
export const diffAriaSnapshots = (previous: string | null, current: string | null): AriaDiff => {
|
|
487
487
|
const flat = (snap: string | null): FlatEntry[] => {
|
|
488
488
|
let tree = parseSnapshot(snap);
|
|
489
489
|
tree = unwrapIgnored(tree);
|
|
@@ -500,7 +500,9 @@ export const diffAriaSnapshots = (previous: string | null, current: string | nul
|
|
|
500
500
|
const currTotals = countBy(curr.map((e) => e.summary));
|
|
501
501
|
const byCount = diffByCount(prevTotals, currTotals);
|
|
502
502
|
const renames = detectRenames(prev, curr, prevTotals, currTotals);
|
|
503
|
-
|
|
503
|
+
const added = [...byCount.added, ...renames.added];
|
|
504
|
+
const removed = [...byCount.removed, ...renames.removed];
|
|
505
|
+
return { text: formatDiff(added, removed, toggled), count: added.length + removed.length + toggled.length };
|
|
504
506
|
};
|
|
505
507
|
|
|
506
508
|
export const detectFocusArea = (snapshot: string | null): FocusAreaResult => {
|
|
@@ -570,10 +572,17 @@ export function parseAriaLocator(ariaStr: string): { role: string; text: string
|
|
|
570
572
|
return { role: match[1], text: match[2] };
|
|
571
573
|
}
|
|
572
574
|
|
|
575
|
+
export const LARGE_ARIA_CHANGE_THRESHOLD = 50;
|
|
576
|
+
|
|
573
577
|
// ─────────────────────────────────────────────────────────────────
|
|
574
578
|
// Types
|
|
575
579
|
// ─────────────────────────────────────────────────────────────────
|
|
576
580
|
|
|
581
|
+
export interface AriaDiff {
|
|
582
|
+
text: string | null;
|
|
583
|
+
count: number;
|
|
584
|
+
}
|
|
585
|
+
|
|
577
586
|
type AriaNode = {
|
|
578
587
|
role: string;
|
|
579
588
|
name?: string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class TTLCache<T> {
|
|
2
|
+
private store = new Map<string, CacheEntry<T>>();
|
|
3
|
+
private ttlMs: number;
|
|
4
|
+
|
|
5
|
+
constructor(ttlMs = Number.POSITIVE_INFINITY) {
|
|
6
|
+
this.ttlMs = ttlMs;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get(key: string): T | undefined {
|
|
10
|
+
const entry = this.store.get(key);
|
|
11
|
+
if (!entry) return undefined;
|
|
12
|
+
if (Date.now() - entry.at > this.ttlMs) {
|
|
13
|
+
this.store.delete(key);
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return entry.value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
set(key: string, value: T): void {
|
|
20
|
+
this.store.set(key, { value, at: Date.now() });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async fetch(key: string, compute: () => Promise<T>): Promise<T> {
|
|
24
|
+
const cached = this.get(key);
|
|
25
|
+
if (cached !== undefined) return cached;
|
|
26
|
+
const value = await compute();
|
|
27
|
+
this.set(key, value);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
delete(key: string): void {
|
|
32
|
+
this.store.delete(key);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
clear(): void {
|
|
36
|
+
this.store.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type CacheEntry<T> = { value: T; at: number };
|
|
@@ -29,6 +29,15 @@ function indent(text: string): string {
|
|
|
29
29
|
.join('\n');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export function formatHeadings(headings: { h1?: string; h2?: string; h3?: string; h4?: string }): string[] {
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
if (headings.h1) lines.push(`H1: ${headings.h1}`);
|
|
35
|
+
if (headings.h2) lines.push(`H2: ${headings.h2}`);
|
|
36
|
+
if (headings.h3) lines.push(`H3: ${headings.h3}`);
|
|
37
|
+
if (headings.h4) lines.push(`H4: ${headings.h4}`);
|
|
38
|
+
return lines;
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
export type ContextMode = 'attached' | 'compact' | 'full';
|
|
33
42
|
|
|
34
43
|
export function formatContextSummary(data: ContextData, mode: ContextMode = 'compact'): string {
|
|
@@ -51,11 +60,7 @@ function formatContextAttached(data: ContextData): string {
|
|
|
51
60
|
}
|
|
52
61
|
lines.push('');
|
|
53
62
|
|
|
54
|
-
const headingLines
|
|
55
|
-
if (data.headings.h1) headingLines.push(`H1: ${data.headings.h1}`);
|
|
56
|
-
if (data.headings.h2) headingLines.push(`H2: ${data.headings.h2}`);
|
|
57
|
-
if (data.headings.h3) headingLines.push(`H3: ${data.headings.h3}`);
|
|
58
|
-
if (data.headings.h4) headingLines.push(`H4: ${data.headings.h4}`);
|
|
63
|
+
const headingLines = formatHeadings(data.headings);
|
|
59
64
|
|
|
60
65
|
if (headingLines.length > 0) {
|
|
61
66
|
lines.push(section('HEADINGS') + chalk.gray(' (Auto-attached)'));
|
|
@@ -116,11 +121,7 @@ function formatContextCompact(data: ContextData): string {
|
|
|
116
121
|
}
|
|
117
122
|
lines.push('');
|
|
118
123
|
|
|
119
|
-
const headingLines
|
|
120
|
-
if (data.headings.h1) headingLines.push(`H1: ${data.headings.h1}`);
|
|
121
|
-
if (data.headings.h2) headingLines.push(`H2: ${data.headings.h2}`);
|
|
122
|
-
if (data.headings.h3) headingLines.push(`H3: ${data.headings.h3}`);
|
|
123
|
-
if (data.headings.h4) headingLines.push(`H4: ${data.headings.h4}`);
|
|
124
|
+
const headingLines = formatHeadings(data.headings);
|
|
124
125
|
|
|
125
126
|
if (headingLines.length > 0) {
|
|
126
127
|
lines.push(section('HEADINGS') + chalk.gray(' (Auto-attached)'));
|
|
@@ -177,11 +178,7 @@ function formatContextFull(data: ContextData): string {
|
|
|
177
178
|
}
|
|
178
179
|
lines.push('');
|
|
179
180
|
|
|
180
|
-
const headingLines
|
|
181
|
-
if (data.headings.h1) headingLines.push(`H1: ${data.headings.h1}`);
|
|
182
|
-
if (data.headings.h2) headingLines.push(`H2: ${data.headings.h2}`);
|
|
183
|
-
if (data.headings.h3) headingLines.push(`H3: ${data.headings.h3}`);
|
|
184
|
-
if (data.headings.h4) headingLines.push(`H4: ${data.headings.h4}`);
|
|
181
|
+
const headingLines = formatHeadings(data.headings);
|
|
185
182
|
|
|
186
183
|
if (headingLines.length > 0) {
|
|
187
184
|
lines.push(section('HEADINGS') + chalk.gray(' (Auto-attached)'));
|
|
@@ -54,11 +54,9 @@ export class HooksRunner {
|
|
|
54
54
|
private async executeHook(hook: Hook, url: string): Promise<void> {
|
|
55
55
|
try {
|
|
56
56
|
if (hook.type === 'playwright') {
|
|
57
|
-
|
|
58
|
-
await hook.hook({ page, url });
|
|
57
|
+
await this.explorer.withPage(async (page) => hook.hook({ page, url }));
|
|
59
58
|
} else {
|
|
60
|
-
|
|
61
|
-
await hook.hook({ I, url });
|
|
59
|
+
await hook.hook({ I: this.explorer.actor, url });
|
|
62
60
|
}
|
|
63
61
|
} catch (error) {
|
|
64
62
|
debugLog(`Hook error: ${error}`);
|
package/src/utils/html.ts
CHANGED
|
@@ -85,8 +85,10 @@ const HIDDEN_CLASSES = new Set(['hidden', 'invisible', 'd-none', 'hide', 'dn', '
|
|
|
85
85
|
|
|
86
86
|
export const EXPLORBOT_ATTRS = {
|
|
87
87
|
area: 'data-explorbot-area',
|
|
88
|
+
coveredBy: 'data-explorbot-covered-by',
|
|
88
89
|
context: 'data-explorbot-context',
|
|
89
90
|
eidx: 'data-explorbot-eidx',
|
|
91
|
+
hit: 'data-explorbot-hit',
|
|
90
92
|
variant: 'data-explorbot-variant',
|
|
91
93
|
} as const;
|
|
92
94
|
|
|
@@ -169,14 +171,49 @@ export type ComponentScopeExtractionConfig = {
|
|
|
169
171
|
limits: typeof HTML_EXTRACTION_LIMITS;
|
|
170
172
|
};
|
|
171
173
|
|
|
174
|
+
export function captureHtmlForSnapshot(): string {
|
|
175
|
+
const clone = document.documentElement.cloneNode(true) as HTMLElement;
|
|
176
|
+
const liveControls = Array.from(document.querySelectorAll('input, textarea, select')) as Array<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>;
|
|
177
|
+
const clonedControls = Array.from(clone.querySelectorAll('input, textarea, select')) as HTMLElement[];
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < liveControls.length; i++) {
|
|
180
|
+
const source = liveControls[i];
|
|
181
|
+
const target = clonedControls[i];
|
|
182
|
+
if (!target) continue;
|
|
183
|
+
|
|
184
|
+
if (source instanceof HTMLInputElement || source instanceof HTMLTextAreaElement) {
|
|
185
|
+
target.setAttribute('data-explorbot-value', source.value);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (source instanceof HTMLInputElement && (source.type === 'checkbox' || source.type === 'radio')) {
|
|
189
|
+
let checked = 'false';
|
|
190
|
+
if (source.checked) checked = 'true';
|
|
191
|
+
target.setAttribute('data-explorbot-checked', checked);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (source instanceof HTMLSelectElement) {
|
|
195
|
+
target.setAttribute(
|
|
196
|
+
'data-explorbot-value',
|
|
197
|
+
Array.from(source.selectedOptions)
|
|
198
|
+
.map((option) => option.value)
|
|
199
|
+
.join(',')
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return clone.outerHTML;
|
|
205
|
+
}
|
|
206
|
+
|
|
172
207
|
export function extractElementData(el: Element, config?: ElementExtractionConfig) {
|
|
173
208
|
const cfg =
|
|
174
209
|
config ||
|
|
175
210
|
({
|
|
176
211
|
attrs: {
|
|
177
212
|
area: 'data-explorbot-area',
|
|
213
|
+
coveredBy: 'data-explorbot-covered-by',
|
|
178
214
|
context: 'data-explorbot-context',
|
|
179
215
|
eidx: 'data-explorbot-eidx',
|
|
216
|
+
hit: 'data-explorbot-hit',
|
|
180
217
|
variant: 'data-explorbot-variant',
|
|
181
218
|
},
|
|
182
219
|
codeEditorMarkers: ['monaco', 'codemirror', 'ace', 'ace_editor', 'code'],
|
|
@@ -246,6 +283,45 @@ export function extractElementData(el: Element, config?: ElementExtractionConfig
|
|
|
246
283
|
return Array.from(tokens).slice(0, 8);
|
|
247
284
|
}
|
|
248
285
|
|
|
286
|
+
function describeOverlappingElement(target: Element): string {
|
|
287
|
+
const parts = [target.tagName.toLowerCase()];
|
|
288
|
+
const role = target.getAttribute('role');
|
|
289
|
+
const ariaLabel = target.getAttribute('aria-label');
|
|
290
|
+
const id = target.getAttribute('id');
|
|
291
|
+
const className = (target.getAttribute('class') || '').split(/\s+/).filter(Boolean).slice(0, 3).join('.');
|
|
292
|
+
|
|
293
|
+
if (id) parts.push(`#${id.slice(0, 40)}`);
|
|
294
|
+
if (className) parts.push(`.${className.slice(0, 80)}`);
|
|
295
|
+
if (role) parts.push(`[role="${role.slice(0, 40)}"]`);
|
|
296
|
+
if (ariaLabel) parts.push(`[aria-label="${ariaLabel.slice(0, 80)}"]`);
|
|
297
|
+
|
|
298
|
+
return parts.join('');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function inspectHitTarget(target: Element, rect: DOMRect): { hit: string; coveredBy?: string } {
|
|
302
|
+
const viewportPoints = [
|
|
303
|
+
{ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
|
|
304
|
+
{ x: rect.left + Math.min(rect.width - 1, Math.max(1, rect.width * 0.25)), y: rect.top + Math.min(rect.height - 1, Math.max(1, rect.height * 0.25)) },
|
|
305
|
+
{ x: rect.left + Math.min(rect.width - 1, Math.max(1, rect.width * 0.75)), y: rect.top + Math.min(rect.height - 1, Math.max(1, rect.height * 0.75)) },
|
|
306
|
+
].filter((point) => point.x >= 0 && point.y >= 0 && point.x <= window.innerWidth && point.y <= window.innerHeight);
|
|
307
|
+
|
|
308
|
+
if (viewportPoints.length === 0) {
|
|
309
|
+
return { hit: 'offscreen' };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
for (const point of viewportPoints) {
|
|
313
|
+
const top = document.elementFromPoint(point.x, point.y);
|
|
314
|
+
if (!top) continue;
|
|
315
|
+
if (top === target || target.contains(top)) {
|
|
316
|
+
return { hit: 'target' };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const centerPoint = viewportPoints[0];
|
|
321
|
+
const top = document.elementFromPoint(centerPoint.x, centerPoint.y);
|
|
322
|
+
return { hit: 'covered', coveredBy: top ? describeOverlappingElement(top) : undefined };
|
|
323
|
+
}
|
|
324
|
+
|
|
249
325
|
function isEmbeddedCodeEditorFrame(target: Element): boolean {
|
|
250
326
|
const src = (target.getAttribute('src') || '').toLowerCase();
|
|
251
327
|
const markerSelector = cfg.codeEditorMarkers.map((marker) => `[class*="${marker}"]`).join(', ');
|
|
@@ -354,6 +430,9 @@ export function extractElementData(el: Element, config?: ElementExtractionConfig
|
|
|
354
430
|
allAttrs[cfg.attrs.area] = areaHints.join('|');
|
|
355
431
|
allAttrs[cfg.attrs.context] = findContextLabel(el);
|
|
356
432
|
allAttrs[cfg.attrs.variant] = collectVariantHints(el).join('|');
|
|
433
|
+
const hitTarget = inspectHitTarget(el, rect);
|
|
434
|
+
allAttrs[cfg.attrs.hit] = hitTarget.hit;
|
|
435
|
+
if (hitTarget.coveredBy) allAttrs[cfg.attrs.coveredBy] = hitTarget.coveredBy;
|
|
357
436
|
|
|
358
437
|
return {
|
|
359
438
|
tag: el.tagName.toLowerCase(),
|