@accomplish_ai/agent-core 0.2.0 → 0.2.2
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 +141 -0
- package/dist/common/constants.d.ts +1 -1
- package/dist/common/constants.d.ts.map +1 -1
- package/dist/common/constants.js +1 -1
- package/dist/common/constants.js.map +1 -1
- package/dist/common/types/index.d.ts +14 -10
- package/dist/common/types/index.d.ts.map +1 -1
- package/dist/common/types/index.js +4 -10
- package/dist/common/types/index.js.map +1 -1
- package/dist/common/utils/index.d.ts +3 -3
- package/dist/common/utils/index.d.ts.map +1 -1
- package/dist/common/utils/index.js +3 -3
- package/dist/common/utils/index.js.map +1 -1
- package/dist/factories/speech.d.ts.map +1 -1
- package/dist/factories/speech.js.map +1 -1
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -20
- package/dist/index.js.map +1 -1
- package/dist/internal/classes/SecureStorage.d.ts.map +1 -1
- package/dist/internal/classes/SecureStorage.js +3 -0
- package/dist/internal/classes/SecureStorage.js.map +1 -1
- package/dist/internal/classes/TaskManager.d.ts +3 -2
- package/dist/internal/classes/TaskManager.d.ts.map +1 -1
- package/dist/internal/classes/TaskManager.js +34 -1
- package/dist/internal/classes/TaskManager.js.map +1 -1
- package/dist/storage/index.d.ts +4 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +4 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/types/log-writer.d.ts +2 -15
- package/dist/types/log-writer.d.ts.map +1 -1
- package/dist/types/skills-manager.d.ts +13 -0
- package/dist/types/skills-manager.d.ts.map +1 -1
- package/dist/types/speech.d.ts +3 -2
- package/dist/types/speech.d.ts.map +1 -1
- package/dist/types/storage.d.ts +70 -0
- package/dist/types/storage.d.ts.map +1 -1
- package/dist/types/task-manager.d.ts +13 -3
- package/dist/types/task-manager.d.ts.map +1 -1
- package/dist/types.d.ts +0 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/index.d.ts +16 -13
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +13 -13
- package/dist/utils/index.js.map +1 -1
- package/mcp-tools/dev-browser/server.cjs +144 -0
- package/package.json +16 -3
- package/mcp-tools/ask-user-question/src/index.ts +0 -183
- package/mcp-tools/ask-user-question/tsconfig.json +0 -12
- package/mcp-tools/complete-task/src/index.ts +0 -92
- package/mcp-tools/dev-browser/src/index.ts +0 -290
- package/mcp-tools/dev-browser/src/relay.ts +0 -652
- package/mcp-tools/dev-browser/src/types.ts +0 -31
- package/mcp-tools/dev-browser/tsconfig.json +0 -36
- package/mcp-tools/dev-browser-mcp/src/index.ts +0 -3940
- package/mcp-tools/dev-browser-mcp/src/snapshot/compactor.test.ts +0 -86
- package/mcp-tools/dev-browser-mcp/src/snapshot/compactor.ts +0 -31
- package/mcp-tools/dev-browser-mcp/src/snapshot/differ.test.ts +0 -178
- package/mcp-tools/dev-browser-mcp/src/snapshot/differ.ts +0 -167
- package/mcp-tools/dev-browser-mcp/src/snapshot/index.ts +0 -19
- package/mcp-tools/dev-browser-mcp/src/snapshot/manager.test.ts +0 -247
- package/mcp-tools/dev-browser-mcp/src/snapshot/manager.ts +0 -131
- package/mcp-tools/dev-browser-mcp/src/snapshot/parser.test.ts +0 -94
- package/mcp-tools/dev-browser-mcp/src/snapshot/parser.ts +0 -81
- package/mcp-tools/dev-browser-mcp/src/snapshot/priority.test.ts +0 -104
- package/mcp-tools/dev-browser-mcp/src/snapshot/priority.ts +0 -84
- package/mcp-tools/dev-browser-mcp/src/snapshot/tokens.test.ts +0 -64
- package/mcp-tools/dev-browser-mcp/src/snapshot/tokens.ts +0 -36
- package/mcp-tools/dev-browser-mcp/src/snapshot/types.ts +0 -89
- package/mcp-tools/dev-browser-mcp/tsconfig.json +0 -15
- package/mcp-tools/file-permission/src/index.ts +0 -125
- package/mcp-tools/file-permission/tsconfig.json +0 -17
- package/mcp-tools/report-checkpoint/src/index.ts +0 -127
- package/mcp-tools/report-checkpoint/tsconfig.json +0 -12
- package/mcp-tools/report-thought/src/index.ts +0 -109
- package/mcp-tools/report-thought/tsconfig.json +0 -12
- package/mcp-tools/start-task/src/index.ts +0 -86
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
// packages/core/mcp-tools/dev-browser-mcp/src/snapshot/manager.test.ts
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
4
|
-
import { SnapshotManager } from './manager.js';
|
|
5
|
-
|
|
6
|
-
describe('SnapshotManager', () => {
|
|
7
|
-
let manager: SnapshotManager;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
manager = new SnapshotManager();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const simpleSnapshot = `- button "Submit" [ref=e1]`;
|
|
14
|
-
|
|
15
|
-
it('returns full snapshot on first call', () => {
|
|
16
|
-
const result = manager.processSnapshot(
|
|
17
|
-
simpleSnapshot,
|
|
18
|
-
'https://example.com',
|
|
19
|
-
'Test Page'
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
expect(result.type).toBe('full');
|
|
23
|
-
expect(result.content).toBe(simpleSnapshot);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('returns diff on second call with same page', () => {
|
|
27
|
-
// First call
|
|
28
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com', 'Test');
|
|
29
|
-
|
|
30
|
-
// Second call - same URL
|
|
31
|
-
const result = manager.processSnapshot(
|
|
32
|
-
simpleSnapshot,
|
|
33
|
-
'https://example.com',
|
|
34
|
-
'Test'
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
expect(result.type).toBe('diff');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('returns full snapshot when URL changes', () => {
|
|
41
|
-
// First call
|
|
42
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com/page1', 'Page 1');
|
|
43
|
-
|
|
44
|
-
// Second call - different URL
|
|
45
|
-
const result = manager.processSnapshot(
|
|
46
|
-
simpleSnapshot,
|
|
47
|
-
'https://example.com/page2',
|
|
48
|
-
'Page 2'
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
expect(result.type).toBe('full');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('returns full snapshot when full_snapshot option is true', () => {
|
|
55
|
-
// First call
|
|
56
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com', 'Test');
|
|
57
|
-
|
|
58
|
-
// Second call with full_snapshot: true
|
|
59
|
-
const result = manager.processSnapshot(
|
|
60
|
-
simpleSnapshot,
|
|
61
|
-
'https://example.com',
|
|
62
|
-
'Test',
|
|
63
|
-
{ fullSnapshot: true }
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(result.type).toBe('full');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('normalizes URLs for same-page detection', () => {
|
|
70
|
-
// First call
|
|
71
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com/page#section1', 'Test');
|
|
72
|
-
|
|
73
|
-
// Second call - same URL, different hash
|
|
74
|
-
const result = manager.processSnapshot(
|
|
75
|
-
simpleSnapshot,
|
|
76
|
-
'https://example.com/page#section2',
|
|
77
|
-
'Test'
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
expect(result.type).toBe('diff');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('resets state correctly', () => {
|
|
84
|
-
// First call
|
|
85
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com', 'Test');
|
|
86
|
-
|
|
87
|
-
// Reset
|
|
88
|
-
manager.reset();
|
|
89
|
-
|
|
90
|
-
// Should act like first call again
|
|
91
|
-
const result = manager.processSnapshot(
|
|
92
|
-
simpleSnapshot,
|
|
93
|
-
'https://example.com',
|
|
94
|
-
'Test'
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
expect(result.type).toBe('full');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('session history', () => {
|
|
101
|
-
it('should track navigation history', () => {
|
|
102
|
-
// Process snapshots for different pages
|
|
103
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com/page1', 'Page 1');
|
|
104
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com/page2', 'Page 2');
|
|
105
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com/page3', 'Page 3');
|
|
106
|
-
|
|
107
|
-
const summary = manager.getSessionSummary();
|
|
108
|
-
expect(summary.history).toContain('Page 1');
|
|
109
|
-
expect(summary.history).toContain('Page 2');
|
|
110
|
-
expect(summary.history).toContain('Page 3');
|
|
111
|
-
expect(summary.pagesVisited).toBe(3);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should limit history to 10 entries', () => {
|
|
115
|
-
for (let i = 0; i < 15; i++) {
|
|
116
|
-
manager.processSnapshot(
|
|
117
|
-
simpleSnapshot,
|
|
118
|
-
`https://example.com/page${i}`,
|
|
119
|
-
`Page ${i}`
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const summary = manager.getSessionSummary();
|
|
124
|
-
expect(summary.pagesVisited).toBe(10);
|
|
125
|
-
expect(summary.history).not.toContain('Page 0');
|
|
126
|
-
expect(summary.history).toContain('Page 14');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should reset history on manager reset', () => {
|
|
130
|
-
manager.processSnapshot(simpleSnapshot, 'https://example.com', 'Home');
|
|
131
|
-
manager.reset();
|
|
132
|
-
|
|
133
|
-
const summary = manager.getSessionSummary();
|
|
134
|
-
expect(summary.pagesVisited).toBe(0);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('full optimization pipeline', () => {
|
|
139
|
-
it('should produce optimized output with all tiers', () => {
|
|
140
|
-
// Create test YAML with various element types
|
|
141
|
-
const yaml1 = `- button "Home" [ref=e1]
|
|
142
|
-
- link "About" [ref=e2]
|
|
143
|
-
- navigation "Main Nav" [ref=e3]`;
|
|
144
|
-
|
|
145
|
-
const yaml2 = `- button "Search" [ref=e4]
|
|
146
|
-
- textbox "Query" [ref=e5]
|
|
147
|
-
- link "Results" [ref=e6]`;
|
|
148
|
-
|
|
149
|
-
// Simulate navigation to first page
|
|
150
|
-
const result1 = manager.processSnapshot(
|
|
151
|
-
yaml1,
|
|
152
|
-
'https://example.com/home',
|
|
153
|
-
'Home',
|
|
154
|
-
{}
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// First snapshot should be full
|
|
158
|
-
expect(result1.type).toBe('full');
|
|
159
|
-
expect(result1.content).toBe(yaml1);
|
|
160
|
-
|
|
161
|
-
// Simulate navigation to second page
|
|
162
|
-
const result2 = manager.processSnapshot(
|
|
163
|
-
yaml2,
|
|
164
|
-
'https://example.com/search',
|
|
165
|
-
'Search',
|
|
166
|
-
{}
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
// New page should also be full snapshot
|
|
170
|
-
expect(result2.type).toBe('full');
|
|
171
|
-
expect(result2.content).toBe(yaml2);
|
|
172
|
-
|
|
173
|
-
// Verify session tracking
|
|
174
|
-
const summary = manager.getSessionSummary();
|
|
175
|
-
expect(summary.pagesVisited).toBe(2);
|
|
176
|
-
expect(summary.history).toContain('Home');
|
|
177
|
-
expect(summary.history).toContain('Search');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should use diff for same-page updates', () => {
|
|
181
|
-
const initialYaml = `- button "Submit" [ref=e1]
|
|
182
|
-
- textbox "Name" [ref=e2]`;
|
|
183
|
-
|
|
184
|
-
const updatedYaml = `- button "Submit" [ref=e1]
|
|
185
|
-
- textbox "Name" [ref=e2]
|
|
186
|
-
- text "Success!" [ref=e3]`;
|
|
187
|
-
|
|
188
|
-
// First snapshot
|
|
189
|
-
const result1 = manager.processSnapshot(
|
|
190
|
-
initialYaml,
|
|
191
|
-
'https://example.com/form',
|
|
192
|
-
'Form Page',
|
|
193
|
-
{}
|
|
194
|
-
);
|
|
195
|
-
expect(result1.type).toBe('full');
|
|
196
|
-
|
|
197
|
-
// Same page update should use diff
|
|
198
|
-
const result2 = manager.processSnapshot(
|
|
199
|
-
updatedYaml,
|
|
200
|
-
'https://example.com/form',
|
|
201
|
-
'Form Page',
|
|
202
|
-
{}
|
|
203
|
-
);
|
|
204
|
-
expect(result2.type).toBe('diff');
|
|
205
|
-
|
|
206
|
-
// Session should show only one unique page visit path
|
|
207
|
-
const summary = manager.getSessionSummary();
|
|
208
|
-
expect(summary.pagesVisited).toBe(2); // Both snapshots recorded
|
|
209
|
-
expect(summary.history).toContain('Form Page');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('should combine navigation tracking with diff optimization', () => {
|
|
213
|
-
// Simulate a real user flow: home -> search -> results -> back to search
|
|
214
|
-
const homeYaml = `- link "Search" [ref=e1]
|
|
215
|
-
- navigation "Main" [ref=e2]`;
|
|
216
|
-
|
|
217
|
-
const searchYaml = `- textbox "Query" [ref=e3]
|
|
218
|
-
- button "Search" [ref=e4]`;
|
|
219
|
-
|
|
220
|
-
const searchWithResultsYaml = `- textbox "Query" [ref=e3]
|
|
221
|
-
- button "Search" [ref=e4]
|
|
222
|
-
- link "Result 1" [ref=e5]
|
|
223
|
-
- link "Result 2" [ref=e6]`;
|
|
224
|
-
|
|
225
|
-
// Visit home
|
|
226
|
-
manager.processSnapshot(homeYaml, 'https://example.com/', 'Home', {});
|
|
227
|
-
|
|
228
|
-
// Navigate to search
|
|
229
|
-
manager.processSnapshot(searchYaml, 'https://example.com/search', 'Search', {});
|
|
230
|
-
|
|
231
|
-
// Update search page with results (same page, should diff)
|
|
232
|
-
const resultUpdate = manager.processSnapshot(
|
|
233
|
-
searchWithResultsYaml,
|
|
234
|
-
'https://example.com/search',
|
|
235
|
-
'Search',
|
|
236
|
-
{}
|
|
237
|
-
);
|
|
238
|
-
expect(resultUpdate.type).toBe('diff');
|
|
239
|
-
|
|
240
|
-
// Verify full navigation history is tracked
|
|
241
|
-
const summary = manager.getSessionSummary();
|
|
242
|
-
expect(summary.pagesVisited).toBe(3);
|
|
243
|
-
expect(summary.history).toContain('Home');
|
|
244
|
-
expect(summary.history).toContain('Search');
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import type { ParsedSnapshot, SnapshotResult, SessionHistoryEntry, SessionSummary } from './types.js';
|
|
2
|
-
import { parseSnapshot } from './parser.js';
|
|
3
|
-
import { diffSnapshots, formatDiff } from './differ.js';
|
|
4
|
-
|
|
5
|
-
const SNAPSHOT_TIMEOUT_MS = 30000;
|
|
6
|
-
|
|
7
|
-
export interface SnapshotManagerOptions {
|
|
8
|
-
fullSnapshot?: boolean;
|
|
9
|
-
interactiveOnly?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class SnapshotManager {
|
|
13
|
-
private lastSnapshot: ParsedSnapshot | null = null;
|
|
14
|
-
private lastTimestamp: number = 0;
|
|
15
|
-
private sessionHistory: SessionHistoryEntry[] = [];
|
|
16
|
-
private readonly MAX_HISTORY_SIZE = 10;
|
|
17
|
-
|
|
18
|
-
processSnapshot(
|
|
19
|
-
rawYaml: string,
|
|
20
|
-
url: string,
|
|
21
|
-
title: string,
|
|
22
|
-
options: SnapshotManagerOptions = {}
|
|
23
|
-
): SnapshotResult {
|
|
24
|
-
const currentSnapshot = parseSnapshot(rawYaml, url, title);
|
|
25
|
-
const now = Date.now();
|
|
26
|
-
|
|
27
|
-
this.recordNavigation(url, title);
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
options.fullSnapshot ||
|
|
31
|
-
!this.lastSnapshot ||
|
|
32
|
-
now - this.lastTimestamp > SNAPSHOT_TIMEOUT_MS
|
|
33
|
-
) {
|
|
34
|
-
this.updateState(currentSnapshot, now);
|
|
35
|
-
return { type: 'full', content: rawYaml };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (this.isSamePage(currentSnapshot.url)) {
|
|
39
|
-
const diff = diffSnapshots(this.lastSnapshot, currentSnapshot);
|
|
40
|
-
|
|
41
|
-
if (!diff) {
|
|
42
|
-
this.updateState(currentSnapshot, now);
|
|
43
|
-
return { type: 'full', content: rawYaml };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
this.updateState(currentSnapshot, now);
|
|
47
|
-
const formattedDiff = formatDiff(diff, url, title);
|
|
48
|
-
return {
|
|
49
|
-
type: 'diff',
|
|
50
|
-
content: formattedDiff,
|
|
51
|
-
unchangedRefs: diff.unchangedRefs,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.updateState(currentSnapshot, now);
|
|
56
|
-
return { type: 'full', content: rawYaml };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
reset(): void {
|
|
60
|
-
this.lastSnapshot = null;
|
|
61
|
-
this.lastTimestamp = 0;
|
|
62
|
-
this.sessionHistory = [];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private isSamePage(currentUrl: string): boolean {
|
|
66
|
-
if (!this.lastSnapshot) return false;
|
|
67
|
-
|
|
68
|
-
const normalizedCurrent = this.normalizeUrl(currentUrl);
|
|
69
|
-
const normalizedLast = this.normalizeUrl(this.lastSnapshot.url);
|
|
70
|
-
|
|
71
|
-
return normalizedCurrent === normalizedLast;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private normalizeUrl(url: string): string {
|
|
75
|
-
try {
|
|
76
|
-
const parsed = new URL(url);
|
|
77
|
-
parsed.hash = '';
|
|
78
|
-
return parsed.toString();
|
|
79
|
-
} catch {
|
|
80
|
-
return url;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private updateState(snapshot: ParsedSnapshot, timestamp: number): void {
|
|
85
|
-
this.lastSnapshot = snapshot;
|
|
86
|
-
this.lastTimestamp = timestamp;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private recordNavigation(url: string, title: string): void {
|
|
90
|
-
this.sessionHistory.push({
|
|
91
|
-
url,
|
|
92
|
-
title,
|
|
93
|
-
timestamp: Date.now(),
|
|
94
|
-
actionsTaken: [],
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
if (this.sessionHistory.length > this.MAX_HISTORY_SIZE) {
|
|
98
|
-
this.sessionHistory = this.sessionHistory.slice(-this.MAX_HISTORY_SIZE);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public getSessionSummary(): SessionSummary {
|
|
103
|
-
if (this.sessionHistory.length === 0) {
|
|
104
|
-
return { history: '', pagesVisited: 0 };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const history = this.sessionHistory
|
|
108
|
-
.map(h => h.title || new URL(h.url).pathname)
|
|
109
|
-
.join(' → ');
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
history,
|
|
113
|
-
pagesVisited: this.sessionHistory.length,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
let snapshotManagerInstance: SnapshotManager | null = null;
|
|
119
|
-
|
|
120
|
-
export function getSnapshotManager(): SnapshotManager {
|
|
121
|
-
if (!snapshotManagerInstance) {
|
|
122
|
-
snapshotManagerInstance = new SnapshotManager();
|
|
123
|
-
}
|
|
124
|
-
return snapshotManagerInstance;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function resetSnapshotManager(): void {
|
|
128
|
-
if (snapshotManagerInstance) {
|
|
129
|
-
snapshotManagerInstance.reset();
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// packages/core/mcp-tools/dev-browser-mcp/src/snapshot/parser.test.ts
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect } from 'vitest';
|
|
4
|
-
import { parseSnapshot, extractTitleFromSnapshot } from './parser.js';
|
|
5
|
-
|
|
6
|
-
describe('parseSnapshot', () => {
|
|
7
|
-
it('parses a simple element with ref', () => {
|
|
8
|
-
const yaml = `- button "Submit" [ref=e1]`;
|
|
9
|
-
const result = parseSnapshot(yaml, 'https://example.com', 'Test Page');
|
|
10
|
-
|
|
11
|
-
expect(result.elements.size).toBe(1);
|
|
12
|
-
expect(result.elements.get('e1')).toEqual({
|
|
13
|
-
ref: 'e1',
|
|
14
|
-
role: 'button',
|
|
15
|
-
name: 'Submit',
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('parses element with value', () => {
|
|
20
|
-
const yaml = `- textbox "Email" [ref=e1]: "user@example.com"`;
|
|
21
|
-
const result = parseSnapshot(yaml, 'https://example.com', 'Test');
|
|
22
|
-
|
|
23
|
-
expect(result.elements.get('e1')?.value).toBe('user@example.com');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('parses disabled attribute', () => {
|
|
27
|
-
const yaml = `- button "Submit" [ref=e1] [disabled]`;
|
|
28
|
-
const result = parseSnapshot(yaml, 'https://example.com', 'Test');
|
|
29
|
-
|
|
30
|
-
expect(result.elements.get('e1')?.disabled).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('parses checked attribute', () => {
|
|
34
|
-
const yaml = `- checkbox "Agree" [ref=e1] [checked]`;
|
|
35
|
-
const result = parseSnapshot(yaml, 'https://example.com', 'Test');
|
|
36
|
-
|
|
37
|
-
expect(result.elements.get('e1')?.checked).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('parses checked=mixed attribute', () => {
|
|
41
|
-
const yaml = `- checkbox "Partial" [ref=e1] [checked=mixed]`;
|
|
42
|
-
const result = parseSnapshot(yaml, 'https://example.com', 'Test');
|
|
43
|
-
|
|
44
|
-
expect(result.elements.get('e1')?.checked).toBe('mixed');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('parses multiple elements', () => {
|
|
48
|
-
const yaml = `
|
|
49
|
-
- textbox "Email" [ref=e1]
|
|
50
|
-
- textbox "Password" [ref=e2]
|
|
51
|
-
- button "Login" [ref=e3]
|
|
52
|
-
`.trim();
|
|
53
|
-
const result = parseSnapshot(yaml, 'https://example.com', 'Login');
|
|
54
|
-
|
|
55
|
-
expect(result.elements.size).toBe(3);
|
|
56
|
-
expect(result.elements.has('e1')).toBe(true);
|
|
57
|
-
expect(result.elements.has('e2')).toBe(true);
|
|
58
|
-
expect(result.elements.has('e3')).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('skips elements without refs', () => {
|
|
62
|
-
const yaml = `
|
|
63
|
-
- heading "Welcome"
|
|
64
|
-
- button "Submit" [ref=e1]
|
|
65
|
-
`.trim();
|
|
66
|
-
const result = parseSnapshot(yaml, 'https://example.com', 'Test');
|
|
67
|
-
|
|
68
|
-
expect(result.elements.size).toBe(1);
|
|
69
|
-
expect(result.elements.has('e1')).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('stores url and title', () => {
|
|
73
|
-
const yaml = `- button "Test" [ref=e1]`;
|
|
74
|
-
const result = parseSnapshot(yaml, 'https://example.com/page', 'My Page');
|
|
75
|
-
|
|
76
|
-
expect(result.url).toBe('https://example.com/page');
|
|
77
|
-
expect(result.title).toBe('My Page');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('extractTitleFromSnapshot', () => {
|
|
82
|
-
it('extracts title from Page Title header', () => {
|
|
83
|
-
const snapshot = `# Page Info
|
|
84
|
-
Page Title: My Login Page
|
|
85
|
-
URL: https://example.com`;
|
|
86
|
-
|
|
87
|
-
expect(extractTitleFromSnapshot(snapshot)).toBe('My Login Page');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('returns empty string if no title found', () => {
|
|
91
|
-
const snapshot = `# Some other content`;
|
|
92
|
-
expect(extractTitleFromSnapshot(snapshot)).toBe('');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import type { SnapshotElement, ParsedSnapshot } from './types.js';
|
|
2
|
-
|
|
3
|
-
export function parseSnapshot(
|
|
4
|
-
yamlSnapshot: string,
|
|
5
|
-
url: string,
|
|
6
|
-
title: string
|
|
7
|
-
): ParsedSnapshot {
|
|
8
|
-
const elements = new Map<string, SnapshotElement>();
|
|
9
|
-
const lines = yamlSnapshot.split('\n');
|
|
10
|
-
|
|
11
|
-
const elementRegex = /^(\s*)-\s+(\w+)(?:\s+"([^"]*)"|\s+'([^']*)')?(.*)$/;
|
|
12
|
-
const refRegex = /\[ref=(e\d+)\]/;
|
|
13
|
-
const valueRegex = /:\s*"([^"]*)"\s*$/;
|
|
14
|
-
const checkedRegex = /\[checked(?:=(\w+))?\]/;
|
|
15
|
-
const disabledRegex = /\[disabled\]/;
|
|
16
|
-
const expandedRegex = /\[expanded\]/;
|
|
17
|
-
const selectedRegex = /\[selected\]/;
|
|
18
|
-
const levelRegex = /\[level=(\d+)\]/;
|
|
19
|
-
const pressedRegex = /\[pressed(?:=(\w+))?\]/;
|
|
20
|
-
|
|
21
|
-
for (const line of lines) {
|
|
22
|
-
const match = line.match(elementRegex);
|
|
23
|
-
if (!match) continue;
|
|
24
|
-
|
|
25
|
-
const [, , role, nameDouble, nameSingle, rest] = match;
|
|
26
|
-
const name = nameDouble ?? nameSingle ?? '';
|
|
27
|
-
|
|
28
|
-
const refMatch = rest.match(refRegex);
|
|
29
|
-
if (!refMatch) continue;
|
|
30
|
-
|
|
31
|
-
const ref = refMatch[1];
|
|
32
|
-
const element: SnapshotElement = { ref, role, name };
|
|
33
|
-
|
|
34
|
-
const valueMatch = line.match(valueRegex);
|
|
35
|
-
if (valueMatch) {
|
|
36
|
-
element.value = valueMatch[1];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const checkedMatch = rest.match(checkedRegex);
|
|
40
|
-
if (checkedMatch) {
|
|
41
|
-
element.checked = checkedMatch[1] === 'mixed' ? 'mixed' : true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (disabledRegex.test(rest)) {
|
|
45
|
-
element.disabled = true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (expandedRegex.test(rest)) {
|
|
49
|
-
element.expanded = true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (selectedRegex.test(rest)) {
|
|
53
|
-
element.selected = true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const levelMatch = rest.match(levelRegex);
|
|
57
|
-
if (levelMatch) {
|
|
58
|
-
element.level = parseInt(levelMatch[1], 10);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const pressedMatch = rest.match(pressedRegex);
|
|
62
|
-
if (pressedMatch) {
|
|
63
|
-
element.pressed = pressedMatch[1] === 'mixed' ? 'mixed' : true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
elements.set(ref, element);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
url,
|
|
71
|
-
title,
|
|
72
|
-
timestamp: Date.now(),
|
|
73
|
-
elements,
|
|
74
|
-
rawYaml: yamlSnapshot,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function extractTitleFromSnapshot(snapshot: string): string {
|
|
79
|
-
const titleMatch = snapshot.match(/(?:Page Title|Title):\s*(.+)/i);
|
|
80
|
-
return titleMatch ? titleMatch[1].trim() : '';
|
|
81
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { getElementPriority, ROLE_PRIORITIES, truncateElements, type TruncatableElement } from './priority';
|
|
3
|
-
|
|
4
|
-
describe('priority scoring', () => {
|
|
5
|
-
describe('getElementPriority', () => {
|
|
6
|
-
it('should score buttons highest', () => {
|
|
7
|
-
const score = getElementPriority('button', true);
|
|
8
|
-
expect(score).toBe(150); // 100 base + 50 viewport bonus
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should score textbox high', () => {
|
|
12
|
-
const score = getElementPriority('textbox', true);
|
|
13
|
-
expect(score).toBe(145); // 95 base + 50 viewport bonus
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should give viewport bonus', () => {
|
|
17
|
-
const inViewport = getElementPriority('link', true);
|
|
18
|
-
const outViewport = getElementPriority('link', false);
|
|
19
|
-
expect(inViewport - outViewport).toBe(50);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should default unknown roles to 50', () => {
|
|
23
|
-
const score = getElementPriority('unknown-role', false);
|
|
24
|
-
expect(score).toBe(50);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should score navigation lower than primary inputs', () => {
|
|
28
|
-
const navigation = getElementPriority('navigation', false);
|
|
29
|
-
const button = getElementPriority('button', false);
|
|
30
|
-
expect(button).toBeGreaterThan(navigation);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('ROLE_PRIORITIES', () => {
|
|
35
|
-
it('should define priorities for all interactive roles', () => {
|
|
36
|
-
const interactiveRoles = [
|
|
37
|
-
'button', 'link', 'textbox', 'checkbox', 'radio',
|
|
38
|
-
'combobox', 'listbox', 'option', 'tab', 'menuitem',
|
|
39
|
-
];
|
|
40
|
-
for (const role of interactiveRoles) {
|
|
41
|
-
expect(ROLE_PRIORITIES[role]).toBeDefined();
|
|
42
|
-
expect(ROLE_PRIORITIES[role]).toBeGreaterThan(0);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('truncateElements', () => {
|
|
49
|
-
const createElements = (count: number, role = 'button', inViewport = true): TruncatableElement[] => {
|
|
50
|
-
return Array.from({ length: count }, (_, i) => ({
|
|
51
|
-
ref: `e${i + 1}`,
|
|
52
|
-
role,
|
|
53
|
-
name: `Element ${i + 1}`,
|
|
54
|
-
inViewport,
|
|
55
|
-
}));
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
it('should return all elements when under limit', () => {
|
|
59
|
-
const elements = createElements(5);
|
|
60
|
-
const result = truncateElements(elements, { maxElements: 10 });
|
|
61
|
-
expect(result.elements).toHaveLength(5);
|
|
62
|
-
expect(result.truncated).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should truncate to maxElements', () => {
|
|
66
|
-
const elements = createElements(100);
|
|
67
|
-
const result = truncateElements(elements, { maxElements: 50 });
|
|
68
|
-
expect(result.elements).toHaveLength(50);
|
|
69
|
-
expect(result.truncated).toBe(true);
|
|
70
|
-
expect(result.totalElements).toBe(100);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should prioritize viewport elements', () => {
|
|
74
|
-
const inViewport = createElements(5, 'button', true);
|
|
75
|
-
const outViewport = createElements(5, 'button', false);
|
|
76
|
-
const mixed = [...outViewport, ...inViewport]; // Out of viewport first
|
|
77
|
-
|
|
78
|
-
const result = truncateElements(mixed, { maxElements: 5 });
|
|
79
|
-
|
|
80
|
-
// Should keep all viewport elements
|
|
81
|
-
expect(result.elements.every(e => e.inViewport)).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should prioritize by role', () => {
|
|
85
|
-
const buttons = createElements(3, 'button', false);
|
|
86
|
-
const links = createElements(3, 'link', false);
|
|
87
|
-
const navs = createElements(3, 'navigation', false);
|
|
88
|
-
const mixed = [...navs, ...links, ...buttons]; // Lowest priority first
|
|
89
|
-
|
|
90
|
-
const result = truncateElements(mixed, { maxElements: 3 });
|
|
91
|
-
|
|
92
|
-
// Should keep buttons (highest priority)
|
|
93
|
-
expect(result.elements.every(e => e.role === 'button')).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should return metadata about truncation', () => {
|
|
97
|
-
const elements = createElements(100);
|
|
98
|
-
const result = truncateElements(elements, { maxElements: 30 });
|
|
99
|
-
|
|
100
|
-
expect(result.totalElements).toBe(100);
|
|
101
|
-
expect(result.includedElements).toBe(30);
|
|
102
|
-
expect(result.truncated).toBe(true);
|
|
103
|
-
});
|
|
104
|
-
});
|