@vscode/component-explorer-cli 0.2.1-15 → 0.2.1-17
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/commands/mcpCommand.d.ts +0 -2
- package/dist/commands/mcpCommand.d.ts.map +1 -1
- package/dist/commands/mcpCommand.js +133 -66
- package/dist/commands/mcpCommand.js.map +1 -1
- package/dist/daemon/lifecycle.d.ts +2 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -1
- package/dist/daemon/lifecycle.js.map +1 -1
- package/dist/daemon/pipeClient.d.ts +2 -0
- package/dist/daemon/pipeClient.d.ts.map +1 -1
- package/dist/daemon/pipeClient.js +18 -1
- package/dist/daemon/pipeClient.js.map +1 -1
- package/dist/mcp/DaemonAccessor.d.ts +22 -0
- package/dist/mcp/DaemonAccessor.d.ts.map +1 -0
- package/dist/mcp/McpServer.d.ts +4 -19
- package/dist/mcp/McpServer.d.ts.map +1 -1
- package/dist/mcp/McpServer.js +116 -204
- package/dist/mcp/McpServer.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/McpServer.js
CHANGED
|
@@ -10,118 +10,13 @@ import '../external/vscode-observables/observables/dist/observableInternal/utils
|
|
|
10
10
|
import '../external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
|
|
11
11
|
import { buildExplorerUrl } from '../utils.js';
|
|
12
12
|
import { TaskManager } from './TaskManager.js';
|
|
13
|
+
import { isPipeConnectionError } from '../daemon/pipeClient.js';
|
|
13
14
|
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Client-local state
|
|
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
|
-
}
|
|
46
|
-
class WatchList {
|
|
47
|
-
_fixtureIds = new Set();
|
|
48
|
-
_hashes = new Map();
|
|
49
|
-
get fixtureIds() { return this._fixtureIds; }
|
|
50
|
-
add(ids) {
|
|
51
|
-
for (const id of ids) {
|
|
52
|
-
this._fixtureIds.add(id);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
remove(ids) {
|
|
56
|
-
for (const id of ids) {
|
|
57
|
-
this._fixtureIds.delete(id);
|
|
58
|
-
this._hashes.delete(id);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
set(ids) {
|
|
62
|
-
this._fixtureIds.clear();
|
|
63
|
-
this._hashes.clear();
|
|
64
|
-
for (const id of ids) {
|
|
65
|
-
this._fixtureIds.add(id);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
getHash(fixtureId) {
|
|
69
|
-
return this._hashes.get(fixtureId);
|
|
70
|
-
}
|
|
71
|
-
setHash(fixtureId, hash) {
|
|
72
|
-
this._hashes.set(fixtureId, hash);
|
|
73
|
-
}
|
|
74
|
-
toJSON() {
|
|
75
|
-
return {
|
|
76
|
-
fixtureIds: [...this._fixtureIds],
|
|
77
|
-
hashes: Object.fromEntries(this._hashes),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function noDaemonError(hint) {
|
|
82
|
-
let text = 'Error: No daemon is currently running.';
|
|
83
|
-
if (hint) {
|
|
84
|
-
text += ` ${hint}`;
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
text += ' Please start the Component Explorer daemon first by running:\n\n' +
|
|
88
|
-
' component-explorer serve --project <config.json>\n\n' +
|
|
89
|
-
'Or start it in the background:\n\n' +
|
|
90
|
-
' component-explorer serve --project <config.json> --background\n\n' +
|
|
91
|
-
'The daemon manages dev servers and enables fixture screenshots.';
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
content: [{ type: 'text', text }],
|
|
95
|
-
isError: true,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// DaemonConnection - wrapper to avoid Proxy issues with observables
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
class DaemonConnection {
|
|
102
|
-
client;
|
|
103
|
-
_stale = false;
|
|
104
|
-
constructor(client) {
|
|
105
|
-
this.client = client;
|
|
106
|
-
}
|
|
107
|
-
get isStale() { return this._stale; }
|
|
108
|
-
markStale() { this._stale = true; }
|
|
109
|
-
}
|
|
110
|
-
function isPipeConnectionError(e) {
|
|
111
|
-
if (!(e instanceof Error)) {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
const code = e.code;
|
|
115
|
-
if (code === 'ENOENT' || code === 'ECONNREFUSED' || code === 'ECONNRESET' || code === 'EPIPE') {
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
return /connect ENOENT|ECONNREFUSED|ECONNRESET|EPIPE/.test(e.message);
|
|
119
|
-
}
|
|
120
15
|
// ---------------------------------------------------------------------------
|
|
121
16
|
// ComponentExplorerMcpServer
|
|
122
17
|
// ---------------------------------------------------------------------------
|
|
123
18
|
class ComponentExplorerMcpServer extends Disposable {
|
|
124
|
-
|
|
19
|
+
_daemon;
|
|
125
20
|
static async create(daemon, options) {
|
|
126
21
|
const server = new ComponentExplorerMcpServer(daemon, options ?? {});
|
|
127
22
|
const transport = new StdioServerTransport();
|
|
@@ -133,16 +28,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
133
28
|
_imageLru = new ImageLruCache(10);
|
|
134
29
|
_taskManager = new TaskManager();
|
|
135
30
|
_taskLastReportedIndex = new Map();
|
|
136
|
-
_pollFn;
|
|
137
|
-
_noAutostartHint;
|
|
138
31
|
_multiSessionTools = [];
|
|
139
32
|
_sessions = [];
|
|
140
33
|
_eventStreamAbortController;
|
|
141
|
-
constructor(
|
|
34
|
+
constructor(_daemon, options) {
|
|
142
35
|
super();
|
|
143
|
-
this.
|
|
144
|
-
this._pollFn = options.pollFn;
|
|
145
|
-
this._noAutostartHint = options.noAutostartHint;
|
|
36
|
+
this._daemon = _daemon;
|
|
146
37
|
this._callTimeoutMs = options.callTimeoutMs ?? ComponentExplorerMcpServer._DEFAULT_CALL_TIMEOUT_MS;
|
|
147
38
|
this._mcp = new McpServer({
|
|
148
39
|
name: 'component-explorer',
|
|
@@ -150,8 +41,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
150
41
|
});
|
|
151
42
|
this._registerTools();
|
|
152
43
|
this._store.add(autorun(async (reader) => {
|
|
153
|
-
const
|
|
154
|
-
await this._onDaemonChanged(
|
|
44
|
+
const client = this._daemon.connection.read(reader);
|
|
45
|
+
await this._onDaemonChanged(client);
|
|
155
46
|
}));
|
|
156
47
|
}
|
|
157
48
|
async _onDaemonChanged(daemon) {
|
|
@@ -173,49 +64,13 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
173
64
|
this._sessions = [];
|
|
174
65
|
}
|
|
175
66
|
}
|
|
176
|
-
_getConnection() {
|
|
177
|
-
const conn = this._daemonConnection.get();
|
|
178
|
-
if (conn?.isStale) {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
return conn;
|
|
182
|
-
}
|
|
183
|
-
async _waitForDaemon() {
|
|
184
|
-
let conn = this._getConnection();
|
|
185
|
-
if (conn) {
|
|
186
|
-
return conn.client;
|
|
187
|
-
}
|
|
188
|
-
if (!this._pollFn) {
|
|
189
|
-
return undefined;
|
|
190
|
-
}
|
|
191
|
-
this._log('debug', { type: 'waiting-for-daemon' });
|
|
192
|
-
const startTime = Date.now();
|
|
193
|
-
const timeout = 3000;
|
|
194
|
-
while (Date.now() - startTime < timeout) {
|
|
195
|
-
await this._pollFn();
|
|
196
|
-
conn = this._getConnection();
|
|
197
|
-
if (conn) {
|
|
198
|
-
return conn.client;
|
|
199
|
-
}
|
|
200
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
201
|
-
}
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
_handleDisconnect() {
|
|
205
|
-
const conn = this._daemonConnection.get();
|
|
206
|
-
if (conn && !conn.isStale) {
|
|
207
|
-
conn.markStale();
|
|
208
|
-
this._sessions = [];
|
|
209
|
-
this._log('debug', { type: 'daemon-connection-lost' });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
67
|
_noDaemonError() {
|
|
213
|
-
return noDaemonError(this.
|
|
68
|
+
return noDaemonError(this._daemon.noDaemonHint);
|
|
214
69
|
}
|
|
215
70
|
static _DEFAULT_CALL_TIMEOUT_MS = 15_000;
|
|
216
71
|
_callTimeoutMs;
|
|
217
72
|
async _withDaemon(fn, options) {
|
|
218
|
-
const daemon = await this.
|
|
73
|
+
const daemon = await this._daemon.getDaemonOrStart();
|
|
219
74
|
if (!daemon) {
|
|
220
75
|
return this._noDaemonError();
|
|
221
76
|
}
|
|
@@ -235,7 +90,6 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
235
90
|
}
|
|
236
91
|
if (isPipeConnectionError(e)) {
|
|
237
92
|
this._log('debug', { type: 'daemon-call-failed', error: String(e) });
|
|
238
|
-
this._handleDisconnect();
|
|
239
93
|
return this._noDaemonError();
|
|
240
94
|
}
|
|
241
95
|
throw e;
|
|
@@ -259,7 +113,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
259
113
|
this._updateSessionSourceTreeId(event.sessionName, event.sourceTreeId);
|
|
260
114
|
}
|
|
261
115
|
if (event.type === 'ref-change' || event.type === 'session-change') {
|
|
262
|
-
await this._refreshSessions();
|
|
116
|
+
await this._refreshSessions(daemon);
|
|
263
117
|
}
|
|
264
118
|
this._log(event.type === 'log' && event.level === 'debug' ? 'debug' : 'info', event);
|
|
265
119
|
}
|
|
@@ -293,24 +147,11 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
293
147
|
s.sourceTreeId = sourceTreeId;
|
|
294
148
|
}
|
|
295
149
|
}
|
|
296
|
-
async _refreshSessions() {
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this._sessions = await conn.client.methods.sessions();
|
|
302
|
-
if (this._sessions.length !== prevCount) {
|
|
303
|
-
this._updateMultiSessionToolVisibility();
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
catch (e) {
|
|
307
|
-
if (isPipeConnectionError(e)) {
|
|
308
|
-
this._handleDisconnect();
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
throw e;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
150
|
+
async _refreshSessions(daemon) {
|
|
151
|
+
const prevCount = this._sessions.length;
|
|
152
|
+
this._sessions = await daemon.methods.sessions();
|
|
153
|
+
if (this._sessions.length !== prevCount) {
|
|
154
|
+
this._updateMultiSessionToolVisibility();
|
|
314
155
|
}
|
|
315
156
|
}
|
|
316
157
|
_updateMultiSessionToolVisibility() {
|
|
@@ -324,14 +165,14 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
324
165
|
}
|
|
325
166
|
}
|
|
326
167
|
}
|
|
327
|
-
async _withSourceTreeRetry(fn) {
|
|
168
|
+
async _withSourceTreeRetry(daemon, fn) {
|
|
328
169
|
try {
|
|
329
170
|
return await fn();
|
|
330
171
|
}
|
|
331
172
|
catch (e) {
|
|
332
173
|
const msg = e instanceof Error ? e.message : String(e);
|
|
333
174
|
if (msg.includes('Source tree changed')) {
|
|
334
|
-
await this._refreshSessions();
|
|
175
|
+
await this._refreshSessions(daemon);
|
|
335
176
|
return await fn();
|
|
336
177
|
}
|
|
337
178
|
throw e;
|
|
@@ -402,7 +243,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
402
243
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
403
244
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
404
245
|
this._log('debug', { type: 'tool-call', tool: 'list_fixtures', sessionName });
|
|
405
|
-
return this._withSourceTreeRetry(async () => {
|
|
246
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
406
247
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
407
248
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
408
249
|
const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
|
|
@@ -433,7 +274,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
433
274
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
434
275
|
this._log('debug', { type: 'tool-call', tool: 'screenshot', fixtureId: args.fixtureId, sessionName });
|
|
435
276
|
this._log('trace', { type: 'tool-args', tool: 'screenshot', args });
|
|
436
|
-
return this._withSourceTreeRetry(async () => {
|
|
277
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
437
278
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
438
279
|
const result = await daemon.methods.screenshots.take({
|
|
439
280
|
fixtureId: args.fixtureId,
|
|
@@ -522,7 +363,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
522
363
|
const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
|
|
523
364
|
const currentSessionName = args.currentSessionName ?? this._defaultCurrentSessionName();
|
|
524
365
|
this._log('debug', { type: 'tool-call', tool: 'compare_screenshot', fixtureId: args.fixtureId, baselineSessionName, currentSessionName });
|
|
525
|
-
return this._withSourceTreeRetry(async () => {
|
|
366
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
526
367
|
const baselineSourceTreeId = args.baselineSourceTreeId ?? this._sourceTreeId(baselineSessionName);
|
|
527
368
|
const currentSourceTreeId = args.currentSourceTreeId ?? this._sourceTreeId(currentSessionName);
|
|
528
369
|
const result = await daemon.methods.screenshots.compare({
|
|
@@ -620,7 +461,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
620
461
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
621
462
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
622
463
|
this._log('debug', { type: 'tool-call', tool: 'review_visual', fixtureId: args.fixtureId, verdict: args.verdict });
|
|
623
|
-
return this._withSourceTreeRetry(async () => {
|
|
464
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
624
465
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
625
466
|
// Get fixture descriptions
|
|
626
467
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
@@ -670,7 +511,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
670
511
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
671
512
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
672
513
|
this._log('debug', { type: 'tool-call', tool: 'check_visuals', sessionName });
|
|
673
|
-
return this._withSourceTreeRetry(async () => {
|
|
514
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
674
515
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
675
516
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
676
517
|
if (listResult.loadError) {
|
|
@@ -744,7 +585,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
744
585
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
745
586
|
this._log('debug', { type: 'tool-call', tool: 'evaluate_js', sessionName, hasFixtureId: !!args.fixtureId });
|
|
746
587
|
this._log('trace', { type: 'tool-args', tool: 'evaluate_js', expressionLength: args.expression.length, fixtureId: args.fixtureId });
|
|
747
|
-
return this._withSourceTreeRetry(async () => {
|
|
588
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
748
589
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
749
590
|
const result = await daemon.methods.evaluate({
|
|
750
591
|
sessionName,
|
|
@@ -853,7 +694,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
853
694
|
}
|
|
854
695
|
const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
|
|
855
696
|
const currentSessionName = args.currentSessionName ?? this._defaultCurrentSessionName();
|
|
856
|
-
return this._withSourceTreeRetry(async () => {
|
|
697
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
857
698
|
const baselineSourceTreeId = args.baselineSourceTreeId ?? this._sourceTreeId(baselineSessionName);
|
|
858
699
|
const currentSourceTreeId = args.currentSourceTreeId ?? this._sourceTreeId(currentSessionName);
|
|
859
700
|
const [baselineResult, currentResult] = await Promise.all([
|
|
@@ -946,7 +787,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
946
787
|
this._updateSessionSourceTreeId(ev.sessionName, ev.sourceTreeId);
|
|
947
788
|
}
|
|
948
789
|
if (ev.type === 'ref-change') {
|
|
949
|
-
const refreshResult = await Promise.race([this._refreshSessions(), timeout]);
|
|
790
|
+
const refreshResult = await Promise.race([this._refreshSessions(daemon), timeout]);
|
|
950
791
|
if (refreshResult === 'timeout') {
|
|
951
792
|
return { content: [{ type: 'text', text: JSON.stringify({ timeout: true, sessionName, sourceTreeId: knownSourceTreeId }, null, 2) }] };
|
|
952
793
|
}
|
|
@@ -1003,8 +844,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1003
844
|
this._mcp.registerTool('sessions', {
|
|
1004
845
|
description: 'List active sessions with their names, URLs, and current sourceTreeIds',
|
|
1005
846
|
annotations: { readOnlyHint: true },
|
|
1006
|
-
}, async () => this._withDaemon(async (
|
|
1007
|
-
await this._refreshSessions();
|
|
847
|
+
}, async () => this._withDaemon(async (daemon) => {
|
|
848
|
+
await this._refreshSessions(daemon);
|
|
1008
849
|
return {
|
|
1009
850
|
content: [{ type: 'text', text: JSON.stringify(this._sessions, null, 2) }],
|
|
1010
851
|
};
|
|
@@ -1104,24 +945,11 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1104
945
|
embedded: z.boolean().optional().describe('If true, returns an embedded single-fixture URL (minimal UI, requires fixtureId). Default: false (full explorer UI).'),
|
|
1105
946
|
},
|
|
1106
947
|
annotations: { readOnlyHint: true },
|
|
1107
|
-
}, async (args) => {
|
|
948
|
+
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1108
949
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
1109
950
|
let session = this._sessions.find(s => s.name === sessionName);
|
|
1110
951
|
if (!session) {
|
|
1111
|
-
|
|
1112
|
-
if (daemon) {
|
|
1113
|
-
try {
|
|
1114
|
-
await this._refreshSessions();
|
|
1115
|
-
}
|
|
1116
|
-
catch (e) {
|
|
1117
|
-
if (isPipeConnectionError(e)) {
|
|
1118
|
-
this._handleDisconnect();
|
|
1119
|
-
}
|
|
1120
|
-
else {
|
|
1121
|
-
throw e;
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
952
|
+
await this._refreshSessions(daemon);
|
|
1125
953
|
session = this._sessions.find(s => s.name === sessionName);
|
|
1126
954
|
if (!session) {
|
|
1127
955
|
return {
|
|
@@ -1170,7 +998,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1170
998
|
return {
|
|
1171
999
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1172
1000
|
};
|
|
1173
|
-
});
|
|
1001
|
+
}));
|
|
1174
1002
|
}
|
|
1175
1003
|
_registerCheckFixtureErrors() {
|
|
1176
1004
|
this._mcp.registerTool('check_fixture_errors', {
|
|
@@ -1186,7 +1014,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1186
1014
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1187
1015
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
1188
1016
|
this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
|
|
1189
|
-
return this._withSourceTreeRetry(async () => {
|
|
1017
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
1190
1018
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
1191
1019
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
1192
1020
|
if (listResult.loadError) {
|
|
@@ -1278,7 +1106,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1278
1106
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1279
1107
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
1280
1108
|
this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
|
|
1281
|
-
return this._withSourceTreeRetry(async () => {
|
|
1109
|
+
return this._withSourceTreeRetry(daemon, async () => {
|
|
1282
1110
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
1283
1111
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
1284
1112
|
if (listResult.loadError) {
|
|
@@ -1459,6 +1287,90 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1459
1287
|
}));
|
|
1460
1288
|
}
|
|
1461
1289
|
}
|
|
1290
|
+
// ---------------------------------------------------------------------------
|
|
1291
|
+
// Client-local state
|
|
1292
|
+
// ---------------------------------------------------------------------------
|
|
1293
|
+
class ImageLruCache {
|
|
1294
|
+
_maxSize;
|
|
1295
|
+
_entries = [];
|
|
1296
|
+
constructor(_maxSize = 10) {
|
|
1297
|
+
this._maxSize = _maxSize;
|
|
1298
|
+
}
|
|
1299
|
+
put(hash, image) {
|
|
1300
|
+
const idx = this._entries.findIndex(e => e.hash === hash);
|
|
1301
|
+
if (idx !== -1) {
|
|
1302
|
+
this._entries.splice(idx, 1);
|
|
1303
|
+
}
|
|
1304
|
+
this._entries.unshift({ hash, image });
|
|
1305
|
+
if (this._entries.length > this._maxSize) {
|
|
1306
|
+
this._entries.length = this._maxSize;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
get(hash) {
|
|
1310
|
+
const idx = this._entries.findIndex(e => e.hash === hash);
|
|
1311
|
+
if (idx === -1) {
|
|
1312
|
+
return undefined;
|
|
1313
|
+
}
|
|
1314
|
+
const [entry] = this._entries.splice(idx, 1);
|
|
1315
|
+
this._entries.unshift(entry);
|
|
1316
|
+
return entry.image;
|
|
1317
|
+
}
|
|
1318
|
+
keys() {
|
|
1319
|
+
return this._entries.map(e => e.hash);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
class WatchList {
|
|
1323
|
+
_fixtureIds = new Set();
|
|
1324
|
+
_hashes = new Map();
|
|
1325
|
+
get fixtureIds() { return this._fixtureIds; }
|
|
1326
|
+
add(ids) {
|
|
1327
|
+
for (const id of ids) {
|
|
1328
|
+
this._fixtureIds.add(id);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
remove(ids) {
|
|
1332
|
+
for (const id of ids) {
|
|
1333
|
+
this._fixtureIds.delete(id);
|
|
1334
|
+
this._hashes.delete(id);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
set(ids) {
|
|
1338
|
+
this._fixtureIds.clear();
|
|
1339
|
+
this._hashes.clear();
|
|
1340
|
+
for (const id of ids) {
|
|
1341
|
+
this._fixtureIds.add(id);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
getHash(fixtureId) {
|
|
1345
|
+
return this._hashes.get(fixtureId);
|
|
1346
|
+
}
|
|
1347
|
+
setHash(fixtureId, hash) {
|
|
1348
|
+
this._hashes.set(fixtureId, hash);
|
|
1349
|
+
}
|
|
1350
|
+
toJSON() {
|
|
1351
|
+
return {
|
|
1352
|
+
fixtureIds: [...this._fixtureIds],
|
|
1353
|
+
hashes: Object.fromEntries(this._hashes),
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function noDaemonError(hint) {
|
|
1358
|
+
let text = 'Error: No daemon is currently running.';
|
|
1359
|
+
if (hint) {
|
|
1360
|
+
text += ` ${hint}`;
|
|
1361
|
+
}
|
|
1362
|
+
else {
|
|
1363
|
+
text += ' Please start the Component Explorer daemon first by running:\n\n' +
|
|
1364
|
+
' component-explorer serve --project <config.json>\n\n' +
|
|
1365
|
+
'Or start it in the background:\n\n' +
|
|
1366
|
+
' component-explorer serve --project <config.json> --background\n\n' +
|
|
1367
|
+
'The daemon manages dev servers and enables fixture screenshots.';
|
|
1368
|
+
}
|
|
1369
|
+
return {
|
|
1370
|
+
content: [{ type: 'text', text }],
|
|
1371
|
+
isError: true,
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1462
1374
|
|
|
1463
|
-
export { ComponentExplorerMcpServer
|
|
1375
|
+
export { ComponentExplorerMcpServer };
|
|
1464
1376
|
//# sourceMappingURL=McpServer.js.map
|