@vscode/component-explorer-cli 0.2.0 → 0.2.1-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/dist/_virtual/_build-info.js +1 -1
- package/dist/browserPage.d.ts +11 -2
- package/dist/browserPage.d.ts.map +1 -1
- package/dist/browserPage.js +26 -8
- package/dist/browserPage.js.map +1 -1
- package/dist/commands/mcpCommand.d.ts.map +1 -1
- package/dist/commands/mcpCommand.js +20 -16
- package/dist/commands/mcpCommand.js.map +1 -1
- package/dist/commands/screenshotCommand.d.ts.map +1 -1
- package/dist/commands/screenshotCommand.js +4 -0
- package/dist/commands/screenshotCommand.js.map +1 -1
- package/dist/commands/serveCommand.d.ts +2 -0
- package/dist/commands/serveCommand.d.ts.map +1 -1
- package/dist/commands/serveCommand.js +75 -24
- package/dist/commands/serveCommand.js.map +1 -1
- package/dist/commands/watchCommand.js +9 -4
- package/dist/commands/watchCommand.js.map +1 -1
- package/dist/component-explorer-config.schema.json +166 -0
- package/dist/componentExplorer.d.ts +8 -1
- package/dist/componentExplorer.d.ts.map +1 -1
- package/dist/componentExplorer.js +34 -5
- package/dist/componentExplorer.js.map +1 -1
- package/dist/daemon/DaemonService.d.ts +25 -4
- package/dist/daemon/DaemonService.d.ts.map +1 -1
- package/dist/daemon/DaemonService.js +57 -16
- package/dist/daemon/DaemonService.js.map +1 -1
- package/dist/daemon/lifecycle.d.ts +2 -1
- package/dist/daemon/lifecycle.d.ts.map +1 -1
- package/dist/daemon/lifecycle.js +41 -6
- package/dist/daemon/lifecycle.js.map +1 -1
- package/dist/explorerSession.d.ts +3 -3
- package/dist/explorerSession.d.ts.map +1 -1
- package/dist/explorerSession.js +26 -9
- package/dist/explorerSession.js.map +1 -1
- package/dist/httpServer.d.ts +12 -6
- package/dist/httpServer.d.ts.map +1 -1
- package/dist/httpServer.js +104 -18
- package/dist/httpServer.js.map +1 -1
- package/dist/httpServer.test.d.ts +2 -0
- package/dist/httpServer.test.d.ts.map +1 -0
- package/dist/mcp/McpServer.d.ts +5 -0
- package/dist/mcp/McpServer.d.ts.map +1 -1
- package/dist/mcp/McpServer.js +218 -0
- package/dist/mcp/McpServer.js.map +1 -1
- package/dist/visualCache.d.ts +34 -0
- package/dist/visualCache.d.ts.map +1 -0
- package/dist/visualCache.js +90 -0
- package/dist/visualCache.js.map +1 -0
- package/dist/watchConfig.d.ts +50 -4
- package/dist/watchConfig.d.ts.map +1 -1
- package/dist/watchConfig.js +72 -23
- package/dist/watchConfig.js.map +1 -1
- package/package.json +1 -1
package/dist/httpServer.js
CHANGED
|
@@ -1,29 +1,116 @@
|
|
|
1
|
-
import './external/vscode-observables/observables/dist/observableInternal/index.js';
|
|
2
|
-
import { observableValue } from './external/vscode-observables/observables/dist/observableInternal/observables/observableValue.js';
|
|
3
|
-
import './external/vscode-observables/observables/dist/observableInternal/debugLocation.js';
|
|
4
|
-
import './external/vscode-observables/observables/dist/observableInternal/observables/derived.js';
|
|
5
|
-
import './external/vscode-observables/observables/dist/observableInternal/utils/utils.js';
|
|
6
|
-
import './external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
|
|
7
1
|
import { nullLogger } from './logger.js';
|
|
8
|
-
import { SourceTreeId } from './sourceTreeId.js';
|
|
9
2
|
|
|
10
3
|
class DefaultComponentExplorerHttpServerFactory {
|
|
11
4
|
async createViteServer(project, options) {
|
|
12
5
|
return ViteComponentExplorerHttpServer.create(project, options);
|
|
13
6
|
}
|
|
14
|
-
async
|
|
15
|
-
|
|
7
|
+
async createHttpServer(url, options) {
|
|
8
|
+
return HttpComponentExplorerServer.create(url, options);
|
|
16
9
|
}
|
|
17
10
|
}
|
|
11
|
+
class HttpComponentExplorerServer {
|
|
12
|
+
url;
|
|
13
|
+
_process;
|
|
14
|
+
static async create(urlTemplate, options) {
|
|
15
|
+
let proc;
|
|
16
|
+
let vars = {};
|
|
17
|
+
if (options?.cmd) {
|
|
18
|
+
const log = options.logger ?? nullLogger;
|
|
19
|
+
log.debug(`Starting HTTP server: ${options.cmd}`);
|
|
20
|
+
log.debug(`HTTP server cwd: ${options.cwd ?? process.cwd()}`);
|
|
21
|
+
const { spawn } = await import('node:child_process');
|
|
22
|
+
proc = spawn(options.cmd, { shell: true, stdio: 'pipe', cwd: options.cwd });
|
|
23
|
+
proc.on('error', (e) => log.log(`HTTP server process error: ${e.message}`));
|
|
24
|
+
proc.on('exit', (code) => log.debug(`HTTP server process exited (code=${code})`));
|
|
25
|
+
if (options.wait?.stdout || options.wait?.stderr) {
|
|
26
|
+
vars = await waitForOutputPattern(proc, options.wait, log);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
proc.stdout?.on('data', (d) => log.trace(d.toString().trimEnd()));
|
|
30
|
+
proc.stderr?.on('data', (d) => log.trace(d.toString().trimEnd()));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const url = resolveUrlTemplate(urlTemplate, vars);
|
|
34
|
+
return new HttpComponentExplorerServer(url, proc);
|
|
35
|
+
}
|
|
36
|
+
constructor(url, _process) {
|
|
37
|
+
this.url = url;
|
|
38
|
+
this._process = _process;
|
|
39
|
+
}
|
|
40
|
+
async dispose() {
|
|
41
|
+
if (this._process && !this._process.killed) {
|
|
42
|
+
this._process.kill();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function resolveUrlTemplate(template, vars) {
|
|
47
|
+
return template.replace(/\$\{var:([^}]+)\}/g, (match, name) => {
|
|
48
|
+
if (!(name in vars)) {
|
|
49
|
+
throw new Error(`Unresolved variable \${var:${name}} in URL. Available: ${Object.keys(vars).join(', ') || '(none)'}`);
|
|
50
|
+
}
|
|
51
|
+
return vars[name];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const _stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').replace(/\x1b\].*?\x07/g, '');
|
|
55
|
+
function waitForOutputPattern(proc, wait, log) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
let resolved = false;
|
|
58
|
+
const vars = {};
|
|
59
|
+
let pendingCount = (wait.stdout ? 1 : 0) + (wait.stderr ? 1 : 0);
|
|
60
|
+
const buffers = { stdout: '', stderr: '' };
|
|
61
|
+
const tryResolve = () => {
|
|
62
|
+
if (resolved || pendingCount > 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
resolved = true;
|
|
66
|
+
proc.stdout?.on('data', (d) => log.trace(d.toString().trimEnd()));
|
|
67
|
+
proc.stderr?.on('data', (d) => log.trace(d.toString().trimEnd()));
|
|
68
|
+
resolve(vars);
|
|
69
|
+
};
|
|
70
|
+
const makeHandler = (stream, pattern) => {
|
|
71
|
+
const handler = (d) => {
|
|
72
|
+
const text = d.toString();
|
|
73
|
+
log.trace(text.trimEnd());
|
|
74
|
+
buffers[stream] += _stripAnsi(text);
|
|
75
|
+
const m = pattern.exec(buffers[stream]);
|
|
76
|
+
if (m) {
|
|
77
|
+
log.debug(`${stream} pattern matched: ${JSON.stringify(m.groups ?? {})}`);
|
|
78
|
+
proc[stream]?.off('data', handler);
|
|
79
|
+
Object.assign(vars, m.groups ?? {});
|
|
80
|
+
pendingCount--;
|
|
81
|
+
tryResolve();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return handler;
|
|
85
|
+
};
|
|
86
|
+
if (wait.stdout) {
|
|
87
|
+
proc.stdout?.on('data', makeHandler('stdout', wait.stdout));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
proc.stdout?.on('data', (d) => log.trace(d.toString().trimEnd()));
|
|
91
|
+
}
|
|
92
|
+
if (wait.stderr) {
|
|
93
|
+
proc.stderr?.on('data', makeHandler('stderr', wait.stderr));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
proc.stderr?.on('data', (d) => log.trace(d.toString().trimEnd()));
|
|
97
|
+
}
|
|
98
|
+
proc.on('exit', (code) => {
|
|
99
|
+
if (!resolved) {
|
|
100
|
+
const details = [wait.stdout ? `stdout buffer:\n${buffers.stdout}` : '', wait.stderr ? `stderr buffer:\n${buffers.stderr}` : ''].filter(Boolean).join('\n');
|
|
101
|
+
reject(new Error(`HTTP server process exited (code=${code}) before output matched wait patterns.\n${details}`));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
18
106
|
class ViteComponentExplorerHttpServer {
|
|
19
107
|
_server;
|
|
20
108
|
url;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
constructor(_server, url, sourceTreeId) {
|
|
109
|
+
onSourceChange;
|
|
110
|
+
constructor(_server, url, onSourceChange) {
|
|
24
111
|
this._server = _server;
|
|
25
112
|
this.url = url;
|
|
26
|
-
this.
|
|
113
|
+
this.onSourceChange = onSourceChange;
|
|
27
114
|
}
|
|
28
115
|
static async create(project, options) {
|
|
29
116
|
const log = options?.logger ?? nullLogger;
|
|
@@ -116,11 +203,10 @@ class ViteComponentExplorerHttpServer {
|
|
|
116
203
|
const resolvedUrls = server.resolvedUrls;
|
|
117
204
|
const url = resolvedUrls?.local[0] ?? `http://localhost:${server.httpServer?.address()?.port}`;
|
|
118
205
|
log.debug(`Vite server started: ${url}`);
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return new ViteComponentExplorerHttpServer(server, url.replace(/\/$/, ''), treeId);
|
|
206
|
+
const onSourceChange = (callback) => {
|
|
207
|
+
server.watcher.on('change', callback);
|
|
208
|
+
};
|
|
209
|
+
return new ViteComponentExplorerHttpServer(server, url.replace(/\/$/, ''), onSourceChange);
|
|
124
210
|
}
|
|
125
211
|
async dispose() {
|
|
126
212
|
await this._server.close();
|
package/dist/httpServer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpServer.js","sources":["../src/httpServer.ts"],"sourcesContent":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"httpServer.js","sources":["../src/httpServer.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpServer.test.d.ts","sourceRoot":"","sources":["../src/httpServer.test.ts"],"names":[],"mappings":""}
|
package/dist/mcp/McpServer.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare class ComponentExplorerMcpServer extends Disposable {
|
|
|
18
18
|
static create(daemon: IObservable<DaemonConnection | undefined>, options?: McpServerOptions): Promise<ComponentExplorerMcpServer>;
|
|
19
19
|
private readonly _mcp;
|
|
20
20
|
private readonly _watchList;
|
|
21
|
+
private readonly _imageLru;
|
|
21
22
|
private readonly _taskManager;
|
|
22
23
|
private readonly _taskLastReportedIndex;
|
|
23
24
|
private readonly _pollFn?;
|
|
@@ -50,6 +51,8 @@ export declare class ComponentExplorerMcpServer extends Disposable {
|
|
|
50
51
|
private _registerScreenshot;
|
|
51
52
|
private _registerCompareScreenshot;
|
|
52
53
|
private _registerApproveDiff;
|
|
54
|
+
private _registerReviewVisual;
|
|
55
|
+
private _registerCheckVisuals;
|
|
53
56
|
private _registerEvaluateJs;
|
|
54
57
|
private _registerDebugReloadPage;
|
|
55
58
|
private _registerWatchAdd;
|
|
@@ -67,5 +70,7 @@ export declare class ComponentExplorerMcpServer extends Disposable {
|
|
|
67
70
|
private _registerCheckStability;
|
|
68
71
|
private _registerCheckTask;
|
|
69
72
|
private _registerCancelTask;
|
|
73
|
+
private _registerDebugGetImageByHash;
|
|
74
|
+
private _registerDebugSetBrowserVisibility;
|
|
70
75
|
}
|
|
71
76
|
//# sourceMappingURL=McpServer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpServer.d.ts","sourceRoot":"","sources":["../../src/mcp/McpServer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAW,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"McpServer.d.ts","sourceRoot":"","sources":["../../src/mcp/McpServer.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/McpServer.js
CHANGED
|
@@ -14,6 +14,35 @@ import { TaskManager } from './TaskManager.js';
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Client-local state
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
|
+
class ImageLruCache {
|
|
18
|
+
_maxSize;
|
|
19
|
+
_entries = [];
|
|
20
|
+
constructor(_maxSize = 10) {
|
|
21
|
+
this._maxSize = _maxSize;
|
|
22
|
+
}
|
|
23
|
+
put(hash, image) {
|
|
24
|
+
const idx = this._entries.findIndex(e => e.hash === hash);
|
|
25
|
+
if (idx !== -1) {
|
|
26
|
+
this._entries.splice(idx, 1);
|
|
27
|
+
}
|
|
28
|
+
this._entries.unshift({ hash, image });
|
|
29
|
+
if (this._entries.length > this._maxSize) {
|
|
30
|
+
this._entries.length = this._maxSize;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
get(hash) {
|
|
34
|
+
const idx = this._entries.findIndex(e => e.hash === hash);
|
|
35
|
+
if (idx === -1) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const [entry] = this._entries.splice(idx, 1);
|
|
39
|
+
this._entries.unshift(entry);
|
|
40
|
+
return entry.image;
|
|
41
|
+
}
|
|
42
|
+
keys() {
|
|
43
|
+
return this._entries.map(e => e.hash);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
17
46
|
class WatchList {
|
|
18
47
|
_fixtureIds = new Set();
|
|
19
48
|
_hashes = new Map();
|
|
@@ -101,6 +130,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
101
130
|
}
|
|
102
131
|
_mcp;
|
|
103
132
|
_watchList = new WatchList();
|
|
133
|
+
_imageLru = new ImageLruCache(10);
|
|
104
134
|
_taskManager = new TaskManager();
|
|
105
135
|
_taskLastReportedIndex = new Map();
|
|
106
136
|
_pollFn;
|
|
@@ -337,6 +367,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
337
367
|
this._registerScreenshot();
|
|
338
368
|
this._registerCompareScreenshot();
|
|
339
369
|
this._registerApproveDiff();
|
|
370
|
+
this._registerReviewVisual();
|
|
371
|
+
this._registerCheckVisuals();
|
|
340
372
|
this._registerEvaluateJs();
|
|
341
373
|
this._registerDebugReloadPage();
|
|
342
374
|
this._registerWatchAdd();
|
|
@@ -353,6 +385,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
353
385
|
this._registerCheckStability();
|
|
354
386
|
this._registerCheckTask();
|
|
355
387
|
this._registerCancelTask();
|
|
388
|
+
this._registerDebugGetImageByHash();
|
|
389
|
+
this._registerDebugSetBrowserVisibility();
|
|
356
390
|
}
|
|
357
391
|
_registerListFixtures() {
|
|
358
392
|
this._mcp.registerTool('list_fixtures', {
|
|
@@ -406,6 +440,10 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
406
440
|
});
|
|
407
441
|
const r = result;
|
|
408
442
|
this._updateSessionSourceTreeId(sessionName, r.sourceTreeId);
|
|
443
|
+
// Cache image for debug_get_image_by_hash
|
|
444
|
+
if (r.hash && r.image) {
|
|
445
|
+
this._imageLru.put(r.hash, r.image);
|
|
446
|
+
}
|
|
409
447
|
const info = {
|
|
410
448
|
hash: r.hash,
|
|
411
449
|
sourceTreeId: r.sourceTreeId,
|
|
@@ -425,6 +463,23 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
425
463
|
if (r.isStable !== undefined) {
|
|
426
464
|
info.isStable = r.isStable;
|
|
427
465
|
}
|
|
466
|
+
// Visual review status
|
|
467
|
+
if (r.hash) {
|
|
468
|
+
try {
|
|
469
|
+
const allFixtures = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
470
|
+
const fixture = allFixtures.find(f => f.fixtureId === args.fixtureId);
|
|
471
|
+
const descriptions = fixture?.expectedVisualDescriptions ?? [];
|
|
472
|
+
const review = await daemon.methods.visualReview.getStatus({
|
|
473
|
+
fixtureId: args.fixtureId,
|
|
474
|
+
expectedVisualDescriptions: [...descriptions],
|
|
475
|
+
screenshotHash: r.hash,
|
|
476
|
+
});
|
|
477
|
+
info.review = review;
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
// Visual review not available — ignore
|
|
481
|
+
}
|
|
482
|
+
}
|
|
428
483
|
const content = [];
|
|
429
484
|
if (r.isStable === false && r.stabilityScreenshots) {
|
|
430
485
|
// Not stable: return all distinct screenshots
|
|
@@ -545,6 +600,127 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
545
600
|
tool.disable();
|
|
546
601
|
this._multiSessionTools.push(tool);
|
|
547
602
|
}
|
|
603
|
+
_registerReviewVisual() {
|
|
604
|
+
this._mcp.registerTool('review_visual', {
|
|
605
|
+
description: 'Approve or reject a fixture\'s screenshot based on its expectedVisualDescriptions. ' +
|
|
606
|
+
'You must take a screenshot first and pass the resulting hash. ' +
|
|
607
|
+
'On approve, caches (expectedVisualDescriptions, screenshotHash) so future runs auto-approve.',
|
|
608
|
+
inputSchema: {
|
|
609
|
+
fixtureId: z.string().describe('The fixture ID'),
|
|
610
|
+
screenshotHash: z.string().describe('The screenshot hash (from a prior screenshot tool call)'),
|
|
611
|
+
verdict: z.enum(['approve', 'reject']).describe('Whether the visual matches expectations'),
|
|
612
|
+
comment: z.string().describe('Reason for the verdict'),
|
|
613
|
+
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
614
|
+
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
615
|
+
},
|
|
616
|
+
}, async (args) => this._withDaemon(async (daemon) => {
|
|
617
|
+
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
618
|
+
this._log('debug', { type: 'tool-call', tool: 'review_visual', fixtureId: args.fixtureId, verdict: args.verdict });
|
|
619
|
+
return this._withSourceTreeRetry(async () => {
|
|
620
|
+
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
621
|
+
// Get fixture descriptions
|
|
622
|
+
const allFixtures = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
623
|
+
const fixture = allFixtures.find(f => f.fixtureId === args.fixtureId);
|
|
624
|
+
if (!fixture) {
|
|
625
|
+
return { content: [{ type: 'text', text: `Fixture not found: ${args.fixtureId}` }], isError: true };
|
|
626
|
+
}
|
|
627
|
+
if (fixture.expectedVisualDescriptions.length === 0) {
|
|
628
|
+
return { content: [{ type: 'text', text: `Fixture ${args.fixtureId} has no expectedVisualDescriptions — nothing to review.` }], isError: true };
|
|
629
|
+
}
|
|
630
|
+
if (args.verdict === 'approve') {
|
|
631
|
+
await daemon.methods.visualReview.approve({
|
|
632
|
+
fixtureId: args.fixtureId,
|
|
633
|
+
expectedVisualDescriptions: [...fixture.expectedVisualDescriptions],
|
|
634
|
+
screenshotHash: args.screenshotHash,
|
|
635
|
+
comment: args.comment,
|
|
636
|
+
});
|
|
637
|
+
return {
|
|
638
|
+
content: [{ type: 'text', text: `Approved: ${args.fixtureId} (hash: ${args.screenshotHash})` }],
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
return {
|
|
643
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
644
|
+
fixtureId: args.fixtureId,
|
|
645
|
+
verdict: 'rejected',
|
|
646
|
+
comment: args.comment,
|
|
647
|
+
screenshotHash: args.screenshotHash,
|
|
648
|
+
expectedVisualDescriptions: fixture.expectedVisualDescriptions,
|
|
649
|
+
}, null, 2) }],
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
_registerCheckVisuals() {
|
|
656
|
+
this._mcp.registerTool('check_visuals', {
|
|
657
|
+
description: 'Batch check visual review status for fixtures that have expectedVisualDescription. ' +
|
|
658
|
+
'Returns lists of approved, needs-review, and no-expectation fixtures.',
|
|
659
|
+
inputSchema: {
|
|
660
|
+
fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
661
|
+
labelPattern: z.string().optional().describe('RegExp to filter fixtures by label'),
|
|
662
|
+
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
663
|
+
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
664
|
+
},
|
|
665
|
+
annotations: { readOnlyHint: true },
|
|
666
|
+
}, async (args) => this._withDaemon(async (daemon) => {
|
|
667
|
+
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
668
|
+
this._log('debug', { type: 'tool-call', tool: 'check_visuals', sessionName });
|
|
669
|
+
return this._withSourceTreeRetry(async () => {
|
|
670
|
+
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
671
|
+
const allFixtures = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
672
|
+
const filtered = this._filterFixtures(allFixtures, args.fixtureIdPattern, args.labelPattern);
|
|
673
|
+
if ('error' in filtered) {
|
|
674
|
+
return { content: [{ type: 'text', text: filtered.error }], isError: true };
|
|
675
|
+
}
|
|
676
|
+
const approved = [];
|
|
677
|
+
const needsReview = [];
|
|
678
|
+
const noExpectation = [];
|
|
679
|
+
// Take screenshots for all fixtures with expectations
|
|
680
|
+
const withExpectations = filtered.fixtures.filter(f => f.expectedVisualDescriptions.length > 0);
|
|
681
|
+
const withoutExpectations = filtered.fixtures.filter(f => f.expectedVisualDescriptions.length === 0);
|
|
682
|
+
for (const f of withoutExpectations) {
|
|
683
|
+
noExpectation.push(f.fixtureId);
|
|
684
|
+
}
|
|
685
|
+
for (const f of withExpectations) {
|
|
686
|
+
try {
|
|
687
|
+
const screenshotResult = await daemon.methods.screenshots.take({
|
|
688
|
+
fixtureId: f.fixtureId, sessionName, sourceTreeId, includeImage: true,
|
|
689
|
+
});
|
|
690
|
+
const hash = screenshotResult.hash;
|
|
691
|
+
const image = screenshotResult.image;
|
|
692
|
+
if (hash && image) {
|
|
693
|
+
this._imageLru.put(hash, image);
|
|
694
|
+
}
|
|
695
|
+
if (!hash) {
|
|
696
|
+
needsReview.push({ fixtureId: f.fixtureId, reason: 'screenshot-failed' });
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
const review = await daemon.methods.visualReview.getStatus({
|
|
700
|
+
fixtureId: f.fixtureId,
|
|
701
|
+
expectedVisualDescriptions: [...f.expectedVisualDescriptions],
|
|
702
|
+
screenshotHash: hash,
|
|
703
|
+
});
|
|
704
|
+
if (review === 'no-expectations') {
|
|
705
|
+
noExpectation.push(f.fixtureId);
|
|
706
|
+
}
|
|
707
|
+
else if (typeof review === 'object' && review.status === 'approved') {
|
|
708
|
+
approved.push({ fixtureId: f.fixtureId, screenshotHash: hash });
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
needsReview.push({ fixtureId: f.fixtureId, screenshotHash: hash, reason: 'needs-review' });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
needsReview.push({ fixtureId: f.fixtureId, reason: 'error' });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
content: [{ type: 'text', text: JSON.stringify({ approved, needsReview, noExpectation }, null, 2) }],
|
|
720
|
+
};
|
|
721
|
+
});
|
|
722
|
+
}));
|
|
723
|
+
}
|
|
548
724
|
_registerEvaluateJs() {
|
|
549
725
|
this._mcp.registerTool('evaluate_js', {
|
|
550
726
|
description: 'Evaluate a JavaScript expression in the browser page where fixtures are rendered, for debugging purposes. ' +
|
|
@@ -1138,6 +1314,48 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1138
1314
|
};
|
|
1139
1315
|
});
|
|
1140
1316
|
}
|
|
1317
|
+
_registerDebugGetImageByHash() {
|
|
1318
|
+
this._mcp.registerTool('debug_get_image_by_hash', {
|
|
1319
|
+
description: 'Retrieve a recently-taken screenshot image by its hash. ' +
|
|
1320
|
+
'Keeps the last ~10 images in an LRU cache. ' +
|
|
1321
|
+
'Useful for debugging when screenshot hashes behave unexpectedly.',
|
|
1322
|
+
inputSchema: {
|
|
1323
|
+
hash: z.string().describe('The screenshot hash to look up'),
|
|
1324
|
+
},
|
|
1325
|
+
annotations: { readOnlyHint: true },
|
|
1326
|
+
}, async (args) => {
|
|
1327
|
+
const image = this._imageLru.get(args.hash);
|
|
1328
|
+
if (!image) {
|
|
1329
|
+
return {
|
|
1330
|
+
content: [{ type: 'text', text: `No cached image for hash '${args.hash}'. Available hashes: ${this._imageLru.keys().join(', ') || '(none)'}` }],
|
|
1331
|
+
isError: true,
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return {
|
|
1335
|
+
content: [
|
|
1336
|
+
{ type: 'text', text: `Image for hash: ${args.hash}` },
|
|
1337
|
+
{ type: 'image', data: image, mimeType: 'image/png' },
|
|
1338
|
+
],
|
|
1339
|
+
};
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
_registerDebugSetBrowserVisibility() {
|
|
1343
|
+
this._mcp.registerTool('debug_set_browser_visibility', {
|
|
1344
|
+
description: 'Show or hide the browser window used for rendering fixtures. ' +
|
|
1345
|
+
'Only use this tool when the user explicitly asks to show or hide the browser. ' +
|
|
1346
|
+
'Do not call this tool automatically or as part of other workflows. ' +
|
|
1347
|
+
'Note: changing visibility closes the current browser instance, so the next screenshot or evaluate_js call will relaunch it.',
|
|
1348
|
+
inputSchema: {
|
|
1349
|
+
visible: z.boolean().describe('true to show the browser window (headed mode), false to hide it (headless mode)'),
|
|
1350
|
+
},
|
|
1351
|
+
annotations: { destructiveHint: true },
|
|
1352
|
+
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1353
|
+
await daemon.methods.setBrowserVisibility({ visible: args.visible });
|
|
1354
|
+
return {
|
|
1355
|
+
content: [{ type: 'text', text: `Browser is now ${args.visible ? 'visible (headed)' : 'hidden (headless)'}.` }],
|
|
1356
|
+
};
|
|
1357
|
+
}));
|
|
1358
|
+
}
|
|
1141
1359
|
}
|
|
1142
1360
|
|
|
1143
1361
|
export { ComponentExplorerMcpServer, DaemonConnection };
|