@vscode/component-explorer-cli 0.2.1-18 → 0.2.1-181

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.
Files changed (281) hide show
  1. package/README.md +130 -12
  2. package/dist/WorktreePool.d.ts +5 -4
  3. package/dist/WorktreePool.d.ts.map +1 -1
  4. package/dist/WorktreePool.js +3 -3
  5. package/dist/WorktreePool.js.map +1 -1
  6. package/dist/_virtual/_build-info.js +1 -1
  7. package/dist/browserPage.d.ts +55 -1
  8. package/dist/browserPage.d.ts.map +1 -1
  9. package/dist/browserPage.js +103 -7
  10. package/dist/browserPage.js.map +1 -1
  11. package/dist/browserPage.test.d.ts +2 -0
  12. package/dist/browserPage.test.d.ts.map +1 -0
  13. package/dist/commands/acceptCommand.d.ts +1 -1
  14. package/dist/commands/acceptCommand.d.ts.map +1 -1
  15. package/dist/commands/acceptCommand.js +21 -14
  16. package/dist/commands/acceptCommand.js.map +1 -1
  17. package/dist/commands/checkStabilityCommand.d.ts +2 -0
  18. package/dist/commands/checkStabilityCommand.d.ts.map +1 -1
  19. package/dist/commands/checkStabilityCommand.js +25 -8
  20. package/dist/commands/checkStabilityCommand.js.map +1 -1
  21. package/dist/commands/compareCommand.d.ts +1 -1
  22. package/dist/commands/compareCommand.d.ts.map +1 -1
  23. package/dist/commands/compareCommand.js +31 -22
  24. package/dist/commands/compareCommand.js.map +1 -1
  25. package/dist/commands/inputArg.d.ts +33 -0
  26. package/dist/commands/inputArg.d.ts.map +1 -0
  27. package/dist/commands/inputArg.js +103 -0
  28. package/dist/commands/inputArg.js.map +1 -0
  29. package/dist/commands/mcpCommand.d.ts.map +1 -1
  30. package/dist/commands/mcpCommand.js +18 -12
  31. package/dist/commands/mcpCommand.js.map +1 -1
  32. package/dist/commands/renderCommand.d.ts +10 -1
  33. package/dist/commands/renderCommand.d.ts.map +1 -1
  34. package/dist/commands/renderCommand.js +390 -61
  35. package/dist/commands/renderCommand.js.map +1 -1
  36. package/dist/commands/renderCommand.test.d.ts +2 -0
  37. package/dist/commands/renderCommand.test.d.ts.map +1 -0
  38. package/dist/commands/serveCommand.d.ts +4 -1
  39. package/dist/commands/serveCommand.d.ts.map +1 -1
  40. package/dist/commands/serveCommand.js +105 -36
  41. package/dist/commands/serveCommand.js.map +1 -1
  42. package/dist/commands/serviceDiffCommitsCommand.d.ts.map +1 -1
  43. package/dist/commands/serviceDiffCommitsCommand.js +10 -9
  44. package/dist/commands/serviceDiffCommitsCommand.js.map +1 -1
  45. package/dist/commands/watchCommand.d.ts +1 -0
  46. package/dist/commands/watchCommand.d.ts.map +1 -1
  47. package/dist/commands/watchCommand.js +31 -20
  48. package/dist/commands/watchCommand.js.map +1 -1
  49. package/dist/comparison.d.ts +4 -3
  50. package/dist/comparison.d.ts.map +1 -1
  51. package/dist/comparison.js +17 -14
  52. package/dist/comparison.js.map +1 -1
  53. package/dist/component-explorer-config.schema.json +55 -2
  54. package/dist/componentExplorer.d.ts +110 -12
  55. package/dist/componentExplorer.d.ts.map +1 -1
  56. package/dist/componentExplorer.js +177 -24
  57. package/dist/componentExplorer.js.map +1 -1
  58. package/dist/config.d.ts +33 -4
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/config.js +64 -27
  61. package/dist/config.js.map +1 -1
  62. package/dist/coverage.d.ts +64 -0
  63. package/dist/coverage.d.ts.map +1 -0
  64. package/dist/coverage.js +212 -0
  65. package/dist/coverage.js.map +1 -0
  66. package/dist/daemon/DaemonService.d.ts +5 -199
  67. package/dist/daemon/DaemonService.d.ts.map +1 -1
  68. package/dist/daemon/DaemonService.js +248 -300
  69. package/dist/daemon/DaemonService.js.map +1 -1
  70. package/dist/daemon/approvalStore.d.ts +2 -1
  71. package/dist/daemon/approvalStore.d.ts.map +1 -1
  72. package/dist/daemon/approvalStore.js +4 -3
  73. package/dist/daemon/approvalStore.js.map +1 -1
  74. package/dist/daemon/client.d.ts +76 -5
  75. package/dist/daemon/client.d.ts.map +1 -1
  76. package/dist/daemon/daemonRpc.test.d.ts +2 -0
  77. package/dist/daemon/daemonRpc.test.d.ts.map +1 -0
  78. package/dist/daemon/endpointServer.d.ts +4 -0
  79. package/dist/daemon/endpointServer.d.ts.map +1 -0
  80. package/dist/daemon/endpointServer.js +24 -0
  81. package/dist/daemon/endpointServer.js.map +1 -0
  82. package/dist/daemon/inProcessClient.d.ts +3 -9
  83. package/dist/daemon/inProcessClient.d.ts.map +1 -1
  84. package/dist/daemon/inProcessClient.js +29 -30
  85. package/dist/daemon/inProcessClient.js.map +1 -1
  86. package/dist/daemon/lifecycle.d.ts +5 -5
  87. package/dist/daemon/lifecycle.d.ts.map +1 -1
  88. package/dist/daemon/lifecycle.js +4 -3
  89. package/dist/daemon/lifecycle.js.map +1 -1
  90. package/dist/daemon/pipeClient.d.ts +2 -3
  91. package/dist/daemon/pipeClient.d.ts.map +1 -1
  92. package/dist/daemon/pipeClient.js +93 -194
  93. package/dist/daemon/pipeClient.js.map +1 -1
  94. package/dist/daemon/pipeName.d.ts +3 -3
  95. package/dist/daemon/pipeName.d.ts.map +1 -1
  96. package/dist/daemon/pipeName.js +6 -5
  97. package/dist/daemon/pipeName.js.map +1 -1
  98. package/dist/daemon/pipeServer.d.ts +2 -2
  99. package/dist/daemon/pipeServer.d.ts.map +1 -1
  100. package/dist/daemon/pipeServer.js +79 -70
  101. package/dist/daemon/pipeServer.js.map +1 -1
  102. package/dist/daemon/registerDaemonConnection.d.ts +7 -0
  103. package/dist/daemon/registerDaemonConnection.d.ts.map +1 -0
  104. package/dist/daemon/registerDaemonConnection.js +37 -0
  105. package/dist/daemon/registerDaemonConnection.js.map +1 -0
  106. package/dist/daemon/transports.test.d.ts +2 -0
  107. package/dist/daemon/transports.test.d.ts.map +1 -0
  108. package/dist/daemon/types.d.ts +1655 -8
  109. package/dist/daemon/types.d.ts.map +1 -1
  110. package/dist/daemon/types.js +4 -0
  111. package/dist/daemon/types.js.map +1 -0
  112. package/dist/daemon/version.d.ts +1 -5
  113. package/dist/daemon/version.d.ts.map +1 -1
  114. package/dist/daemon/version.js +3 -7
  115. package/dist/daemon/version.js.map +1 -1
  116. package/dist/dependencyInstaller.d.ts +2 -1
  117. package/dist/dependencyInstaller.d.ts.map +1 -1
  118. package/dist/dependencyInstaller.js +4 -4
  119. package/dist/dependencyInstaller.js.map +1 -1
  120. package/dist/explorerSession.d.ts +5 -1
  121. package/dist/explorerSession.d.ts.map +1 -1
  122. package/dist/explorerSession.js +24 -7
  123. package/dist/explorerSession.js.map +1 -1
  124. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js +2523 -0
  125. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/inspectionHost-BA6ZmC_P.js.map +1 -0
  126. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js +1871 -0
  127. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/src-DYVSK_tz.js.map +1 -0
  128. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js +220 -0
  129. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/chunks/webSocketTransport-Ct4OIdG9.js.map +1 -0
  130. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js +721 -0
  131. package/dist/external/linkrpc/typescript/packages/linkrpc/dist/node.js.map +1 -0
  132. package/dist/git/gitCommitResolver.d.ts +2 -1
  133. package/dist/git/gitCommitResolver.d.ts.map +1 -1
  134. package/dist/git/gitCommitResolver.js +8 -8
  135. package/dist/git/gitCommitResolver.js.map +1 -1
  136. package/dist/git/gitIndexResolver.d.ts +2 -1
  137. package/dist/git/gitIndexResolver.d.ts.map +1 -1
  138. package/dist/git/gitIndexResolver.js +7 -7
  139. package/dist/git/gitIndexResolver.js.map +1 -1
  140. package/dist/git/gitService.d.ts +5 -4
  141. package/dist/git/gitService.d.ts.map +1 -1
  142. package/dist/git/gitService.js.map +1 -1
  143. package/dist/git/gitUtils.d.ts +6 -5
  144. package/dist/git/gitUtils.d.ts.map +1 -1
  145. package/dist/git/gitUtils.js +5 -4
  146. package/dist/git/gitUtils.js.map +1 -1
  147. package/dist/git/gitWorktreeManager.d.ts +15 -14
  148. package/dist/git/gitWorktreeManager.d.ts.map +1 -1
  149. package/dist/git/gitWorktreeManager.js +10 -11
  150. package/dist/git/gitWorktreeManager.js.map +1 -1
  151. package/dist/git/testUtils.d.ts +2 -1
  152. package/dist/git/testUtils.d.ts.map +1 -1
  153. package/dist/index.d.ts +0 -2
  154. package/dist/index.js +10 -1
  155. package/dist/index.js.map +1 -1
  156. package/dist/js-component-explorer/packages/common/dist/daemonRpc.js +165 -0
  157. package/dist/js-component-explorer/packages/common/dist/daemonRpc.js.map +1 -0
  158. package/dist/js-component-explorer/packages/common/dist/explorerUrl.js +21 -0
  159. package/dist/js-component-explorer/packages/common/dist/explorerUrl.js.map +1 -0
  160. package/dist/js-component-explorer/packages/common/dist/renderManifest.js +68 -0
  161. package/dist/js-component-explorer/packages/common/dist/renderManifest.js.map +1 -0
  162. package/dist/js-component-explorer/packages/semver/dist/index.js.map +1 -0
  163. package/dist/manifest.schema.json +44 -16
  164. package/dist/mcp/DaemonAccessor.d.ts +2 -3
  165. package/dist/mcp/DaemonAccessor.d.ts.map +1 -1
  166. package/dist/mcp/McpServer.d.ts +1 -1
  167. package/dist/mcp/McpServer.d.ts.map +1 -1
  168. package/dist/mcp/McpServer.js +252 -159
  169. package/dist/mcp/McpServer.js.map +1 -1
  170. package/dist/path.d.ts +30 -0
  171. package/dist/path.d.ts.map +1 -0
  172. package/dist/path.js +78 -0
  173. package/dist/path.js.map +1 -0
  174. package/dist/processTree.d.ts +6 -1
  175. package/dist/processTree.d.ts.map +1 -1
  176. package/dist/processTree.js +48 -5
  177. package/dist/processTree.js.map +1 -1
  178. package/dist/renderManifest.d.ts +70 -5
  179. package/dist/renderManifest.js +1 -1
  180. package/dist/server/httpServer.d.ts +1 -1
  181. package/dist/server/httpServer.d.ts.map +1 -1
  182. package/dist/server/httpServer.js +4 -8
  183. package/dist/server/httpServer.js.map +1 -1
  184. package/dist/server/serverConfig.d.ts +23 -8
  185. package/dist/server/serverConfig.d.ts.map +1 -1
  186. package/dist/server/serverConfig.js +11 -18
  187. package/dist/server/serverConfig.js.map +1 -1
  188. package/dist/server/viteServer.d.ts +16 -3
  189. package/dist/server/viteServer.d.ts.map +1 -1
  190. package/dist/server/viteServer.js +86 -5
  191. package/dist/server/viteServer.js.map +1 -1
  192. package/dist/storage.d.ts +2 -1
  193. package/dist/storage.d.ts.map +1 -1
  194. package/dist/storage.js +1 -1
  195. package/dist/storage.js.map +1 -1
  196. package/dist/utils.d.ts +3 -30
  197. package/dist/utils.d.ts.map +1 -1
  198. package/dist/utils.js +4 -24
  199. package/dist/utils.js.map +1 -1
  200. package/dist/visualCache.d.ts +2 -1
  201. package/dist/visualCache.d.ts.map +1 -1
  202. package/dist/visualCache.js +4 -3
  203. package/dist/visualCache.js.map +1 -1
  204. package/dist/viteProjectRef.d.ts +4 -12
  205. package/dist/viteProjectRef.d.ts.map +1 -1
  206. package/dist/viteProjectRef.js +9 -17
  207. package/dist/viteProjectRef.js.map +1 -1
  208. package/dist/vscode-observables/observables/dist/disposables.js.map +1 -0
  209. package/dist/vscode-observables/observables/dist/observableInternal/base.js.map +1 -0
  210. package/dist/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +1 -0
  211. package/dist/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +1 -0
  212. package/dist/vscode-observables/observables/dist/observableInternal/debugName.js.map +1 -0
  213. package/dist/vscode-observables/observables/dist/observableInternal/index.js.map +1 -0
  214. package/dist/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +1 -0
  215. package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/debugGetDependencyGraph.js +1 -1
  216. package/dist/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +1 -0
  217. package/dist/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +1 -0
  218. package/dist/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +1 -0
  219. package/dist/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +1 -0
  220. package/dist/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +1 -0
  221. package/dist/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +1 -0
  222. package/dist/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +1 -0
  223. package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js → vscode-observables/observables/dist/observableInternal/reactions/createEffect.js} +9 -5
  224. package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffect.js.map +1 -0
  225. package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js → vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js} +1 -1
  226. package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js.map +1 -0
  227. package/dist/vscode-observables/observables/dist/observableInternal/transaction.js.map +1 -0
  228. package/dist/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +1 -0
  229. package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utilsCancellation.js +2 -2
  230. package/dist/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +1 -0
  231. package/package.json +18 -15
  232. package/dist/commands/artifactCommand.d.ts +0 -14
  233. package/dist/commands/artifactCommand.d.ts.map +0 -1
  234. package/dist/external/vscode-observables/observables/dist/disposables.js.map +0 -1
  235. package/dist/external/vscode-observables/observables/dist/observableInternal/base.js.map +0 -1
  236. package/dist/external/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +0 -1
  237. package/dist/external/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +0 -1
  238. package/dist/external/vscode-observables/observables/dist/observableInternal/debugName.js.map +0 -1
  239. package/dist/external/vscode-observables/observables/dist/observableInternal/index.js.map +0 -1
  240. package/dist/external/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +0 -1
  241. package/dist/external/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +0 -1
  242. package/dist/external/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +0 -1
  243. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +0 -1
  244. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +0 -1
  245. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +0 -1
  246. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +0 -1
  247. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +0 -1
  248. package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js.map +0 -1
  249. package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js.map +0 -1
  250. package/dist/external/vscode-observables/observables/dist/observableInternal/transaction.js.map +0 -1
  251. package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +0 -1
  252. package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +0 -1
  253. package/dist/formatValue.js +0 -96
  254. package/dist/formatValue.js.map +0 -1
  255. package/dist/packages/common/dist/renderManifest.js +0 -46
  256. package/dist/packages/common/dist/renderManifest.js.map +0 -1
  257. package/dist/packages/semver/dist/index.js.map +0 -1
  258. package/dist/packages/simple-api/dist/chunk-FJ7AVNQE.js +0 -138
  259. package/dist/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +0 -1
  260. package/dist/packages/simple-api/dist/chunk-TTRCY65Z.js +0 -27
  261. package/dist/packages/simple-api/dist/chunk-TTRCY65Z.js.map +0 -1
  262. package/dist/packages/simple-api/dist/chunk-WNXMRXWV.js +0 -28
  263. package/dist/packages/simple-api/dist/chunk-WNXMRXWV.js.map +0 -1
  264. package/dist/packages/simple-api/dist/express.js +0 -104
  265. package/dist/packages/simple-api/dist/express.js.map +0 -1
  266. /package/dist/{packages → js-component-explorer/packages}/semver/dist/index.js +0 -0
  267. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/disposables.js +0 -0
  268. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/base.js +0 -0
  269. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/commonFacade/deps.js +0 -0
  270. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugLocation.js +0 -0
  271. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugName.js +0 -0
  272. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/index.js +0 -0
  273. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/consoleObservableLogger.js +0 -0
  274. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/logging.js +0 -0
  275. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/baseObservable.js +0 -0
  276. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derived.js +0 -0
  277. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derivedImpl.js +0 -0
  278. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableFromEvent.js +0 -0
  279. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableValue.js +0 -0
  280. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/transaction.js +0 -0
  281. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utils.js +0 -0
@@ -1,14 +1,16 @@
1
1
  import { z } from 'zod';
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import '../external/vscode-observables/observables/dist/observableInternal/index.js';
5
- import { Disposable } from '../external/vscode-observables/observables/dist/disposables.js';
6
- import '../external/vscode-observables/observables/dist/observableInternal/debugLocation.js';
7
- import { autorun } from '../external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js';
8
- import '../external/vscode-observables/observables/dist/observableInternal/observables/derived.js';
9
- import '../external/vscode-observables/observables/dist/observableInternal/utils/utils.js';
10
- import '../external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
11
- import { buildExplorerUrl } from '../utils.js';
4
+ import '../vscode-observables/observables/dist/observableInternal/index.js';
5
+ import { Disposable } from '../vscode-observables/observables/dist/disposables.js';
6
+ import '../vscode-observables/observables/dist/observableInternal/debugLocation.js';
7
+ import { autorun } from '../vscode-observables/observables/dist/observableInternal/reactions/createEffect.js';
8
+ import '../vscode-observables/observables/dist/observableInternal/observables/derived.js';
9
+ import '../vscode-observables/observables/dist/observableInternal/utils/utils.js';
10
+ import '../vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
11
+ import '../js-component-explorer/packages/common/dist/renderManifest.js';
12
+ import { buildExplorerUrl } from '../js-component-explorer/packages/common/dist/explorerUrl.js';
13
+ import { EXPLORER_ROUTE } from '../utils.js';
12
14
  import { TaskManager } from './TaskManager.js';
13
15
  import { isPipeConnectionError } from '../daemon/pipeClient.js';
14
16
 
@@ -30,7 +32,7 @@ class ComponentExplorerMcpServer extends Disposable {
30
32
  _taskLastReportedIndex = new Map();
31
33
  _multiSessionTools = [];
32
34
  _sessions = [];
33
- _eventStreamAbortController;
35
+ _stopEventListener;
34
36
  constructor(_daemon, options) {
35
37
  super();
36
38
  this._daemon = _daemon;
@@ -40,21 +42,26 @@ class ComponentExplorerMcpServer extends Disposable {
40
42
  version: '0.1.0',
41
43
  });
42
44
  this._registerTools();
45
+ this._store.add({ dispose: () => this._stopEventListener?.() });
43
46
  this._store.add(autorun(async (reader) => {
44
47
  const client = this._daemon.connection.read(reader);
45
48
  await this._onDaemonChanged(client);
46
49
  }));
47
50
  }
48
51
  async _onDaemonChanged(daemon) {
49
- this._eventStreamAbortController?.abort();
50
- this._eventStreamAbortController = undefined;
52
+ this._stopEventListener?.();
53
+ this._stopEventListener = undefined;
51
54
  if (!daemon) {
52
55
  this._sessions = [];
53
56
  this._log('info', { type: 'daemon-disconnected' });
54
57
  return;
55
58
  }
56
59
  try {
57
- this._sessions = await daemon.methods.sessions();
60
+ const sessions = await daemon.methods.sessions({});
61
+ if (this._daemon.connection.get() !== daemon) {
62
+ return;
63
+ }
64
+ this._sessions = sessions;
58
65
  this._log('debug', { type: 'daemon-connected', sessions: this._sessions.length });
59
66
  this._updateMultiSessionToolVisibility();
60
67
  this._startEventListener(daemon);
@@ -100,30 +107,26 @@ class ComponentExplorerMcpServer extends Disposable {
100
107
  this._mcp.sendLoggingMessage({ level: mcpLevel, logger: 'daemon', data }).catch(() => { });
101
108
  }
102
109
  _startEventListener(daemon) {
103
- const controller = new AbortController();
104
- this._eventStreamAbortController = controller;
105
- (async () => {
106
- try {
107
- const stream = await daemon.methods.events();
108
- for await (const raw of stream) {
109
- if (controller.signal.aborted)
110
- break;
111
- const event = raw;
112
- if (event.type === 'source-change' && event.sessionName && event.sourceTreeId) {
113
- this._updateSessionSourceTreeId(event.sessionName, event.sourceTreeId);
114
- }
115
- if (event.type === 'ref-change' || event.type === 'session-change') {
116
- await this._refreshSessions(daemon);
117
- }
118
- this._log(event.type === 'log' && event.level === 'debug' ? 'debug' : 'info', event);
110
+ const call = daemon.methods.events({}, { onMessage: event => {
111
+ if (this._daemon.connection.get() !== daemon) {
112
+ return;
119
113
  }
120
- }
121
- catch (e) {
122
- if (!controller.signal.aborted) {
123
- this._log('info', { type: 'event-stream-error', error: String(e) });
114
+ if (event.type === 'source-change') {
115
+ this._updateSessionSourceTreeId(event.sessionName, event.sourceTreeId);
116
+ }
117
+ if (event.type === 'ref-change' || event.type === 'session-change') {
118
+ void this._refreshSessions(daemon).catch(e => this._log('info', { type: 'session-refresh-error', error: String(e) }));
124
119
  }
120
+ this._log(event.type === 'log' && event.level === 'debug' ? 'debug' : 'info', event);
121
+ } });
122
+ this._stopEventListener = () => {
123
+ void call.cancel('listener disposed').finally(() => call.dispose?.('listener disposed')).catch(() => { });
124
+ };
125
+ void call.catch(e => {
126
+ if (this._daemon.connection.get() === daemon) {
127
+ this._log('info', { type: 'event-stream-error', error: String(e) });
125
128
  }
126
- })();
129
+ });
127
130
  }
128
131
  // -- Helpers --------------------------------------------------------------
129
132
  _defaultSessionName() {
@@ -149,7 +152,11 @@ class ComponentExplorerMcpServer extends Disposable {
149
152
  }
150
153
  async _refreshSessions(daemon) {
151
154
  const prevCount = this._sessions.length;
152
- this._sessions = await daemon.methods.sessions();
155
+ const sessions = await daemon.methods.sessions({});
156
+ if (this._daemon.connection.get() !== daemon) {
157
+ return;
158
+ }
159
+ this._sessions = sessions;
153
160
  if (this._sessions.length !== prevCount) {
154
161
  this._updateMultiSessionToolVisibility();
155
162
  }
@@ -179,23 +186,23 @@ class ComponentExplorerMcpServer extends Disposable {
179
186
  }
180
187
  }
181
188
  // -- Tool registration ---------------------------------------------------
182
- _filterFixtures(allFixtures, fixtureIdPattern, labelPattern) {
189
+ _filterFixtures(allFixtures, fixtureIdRegexStr, labelRegexStr) {
183
190
  let fixtureIdRegex;
184
- if (fixtureIdPattern) {
191
+ if (fixtureIdRegexStr) {
185
192
  try {
186
- fixtureIdRegex = new RegExp(fixtureIdPattern);
193
+ fixtureIdRegex = new RegExp(fixtureIdRegexStr);
187
194
  }
188
195
  catch {
189
- return { error: `Error: Invalid fixtureIdPattern: ${fixtureIdPattern}` };
196
+ return { error: `Error: Invalid fixtureIdRegex: ${fixtureIdRegexStr}` };
190
197
  }
191
198
  }
192
199
  let labelRegex;
193
- if (labelPattern) {
200
+ if (labelRegexStr) {
194
201
  try {
195
- labelRegex = new RegExp(labelPattern);
202
+ labelRegex = new RegExp(labelRegexStr);
196
203
  }
197
204
  catch {
198
- return { error: `Error: Invalid labelPattern: ${labelPattern}` };
205
+ return { error: `Error: Invalid labelRegex: ${labelRegexStr}` };
199
206
  }
200
207
  }
201
208
  return {
@@ -233,12 +240,12 @@ class ComponentExplorerMcpServer extends Disposable {
233
240
  _registerListFixtures() {
234
241
  this._mcp.registerTool('list_fixtures', {
235
242
  description: 'List all fixtures from a session',
236
- inputSchema: {
237
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
238
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
243
+ inputSchema: z.strictObject({
244
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
245
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
239
246
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
240
247
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
241
- },
248
+ }),
242
249
  annotations: { readOnlyHint: true },
243
250
  }, async (args) => this._withDaemon(async (daemon) => {
244
251
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -246,7 +253,7 @@ class ComponentExplorerMcpServer extends Disposable {
246
253
  return this._withSourceTreeRetry(daemon, async () => {
247
254
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
248
255
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
249
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
256
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
250
257
  if ('error' in filtered) {
251
258
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
252
259
  }
@@ -262,13 +269,22 @@ class ComponentExplorerMcpServer extends Disposable {
262
269
  _registerScreenshot() {
263
270
  this._mcp.registerTool('screenshot', {
264
271
  description: 'Take a screenshot of a single fixture. ' +
265
- 'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. ',
266
- inputSchema: {
272
+ 'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. ' +
273
+ 'By default the fixture stays mounted after the screenshot (so a follow-up `evaluate_js` call can still operate on it); ' +
274
+ 'pass `disposeAfter: true` to dispose immediately and surface any teardown errors as `currentDispose` in the result. ' +
275
+ 'Errors / events captured while disposing the previously-mounted fixture (the implicit dispose at the start of every render) are reported as `previousDispose`.',
276
+ inputSchema: z.strictObject({
267
277
  fixtureId: z.string().describe('The fixture ID'),
268
278
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
269
279
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
270
280
  stabilityCheck: z.boolean().optional().describe('If true, takes three screenshots (at render time, ~500ms, ~3000ms) to check visual stability. Expensive (~3s extra) — only use it when you need to verify rendering stability..'),
271
- },
281
+ includeConsoleLog: z.boolean().optional().describe('If true, include console.log events in the returned events array. Defaults to false — console.log entries are filtered out to reduce noise.'),
282
+ input: z.unknown().optional().describe('Arbitrary JSON-serializable data passed to the fixture as `RenderContext.input`. ' +
283
+ 'The fixture decides how to interpret it (e.g. theme switch, scenario selection, mock data).'),
284
+ disposeAfter: z.boolean().optional().describe('If true, dispose the fixture after taking the screenshot. ' +
285
+ 'Default: false — the fixture stays mounted until the next render, so `evaluate_js` can still operate on it. ' +
286
+ 'Use this when validating fixture teardown: dispose errors / events appear in `currentDispose`.'),
287
+ }),
272
288
  annotations: { readOnlyHint: true },
273
289
  }, async (args) => this._withDaemon(async (daemon) => {
274
290
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -282,6 +298,8 @@ class ComponentExplorerMcpServer extends Disposable {
282
298
  sourceTreeId,
283
299
  includeImage: true,
284
300
  stabilityCheck: args.stabilityCheck,
301
+ input: args.input,
302
+ disposeAfter: args.disposeAfter,
285
303
  });
286
304
  const r = result;
287
305
  this._updateSessionSourceTreeId(sessionName, r.sourceTreeId);
@@ -289,6 +307,7 @@ class ComponentExplorerMcpServer extends Disposable {
289
307
  if (r.hash && r.image) {
290
308
  this._imageLru.put(r.hash, r.image);
291
309
  }
310
+ const filterEvents = (events) => args.includeConsoleLog ? events : events.filter(e => e.type !== 'console.log');
292
311
  const info = {
293
312
  hash: r.hash,
294
313
  sourceTreeId: r.sourceTreeId,
@@ -300,14 +319,37 @@ class ComponentExplorerMcpServer extends Disposable {
300
319
  info.error = r.error;
301
320
  }
302
321
  if (r.events && r.events.length > 0) {
303
- info.events = r.events;
322
+ const filtered = filterEvents(r.events);
323
+ if (filtered.length > 0) {
324
+ info.events = filtered;
325
+ }
304
326
  }
305
- if (r.resultData !== undefined) {
306
- info.resultData = r.resultData;
327
+ if (r.output !== undefined) {
328
+ info.output = r.output;
307
329
  }
308
330
  if (r.isStable !== undefined) {
309
331
  info.isStable = r.isStable;
310
332
  }
333
+ if (r.previousDispose) {
334
+ const filtered = filterEvents(r.previousDispose.events);
335
+ if (r.previousDispose.hasError || r.previousDispose.errors.length > 0 || filtered.length > 0) {
336
+ info.previousDispose = {
337
+ hasError: r.previousDispose.hasError,
338
+ errors: r.previousDispose.errors,
339
+ events: filtered,
340
+ };
341
+ }
342
+ }
343
+ if (r.currentDispose) {
344
+ const filtered = filterEvents(r.currentDispose.events);
345
+ if (r.currentDispose.hasError || r.currentDispose.errors.length > 0 || filtered.length > 0) {
346
+ info.currentDispose = {
347
+ hasError: r.currentDispose.hasError,
348
+ errors: r.currentDispose.errors,
349
+ events: filtered,
350
+ };
351
+ }
352
+ }
311
353
  // Visual review status
312
354
  if (r.hash) {
313
355
  try {
@@ -351,13 +393,13 @@ class ComponentExplorerMcpServer extends Disposable {
351
393
  _registerCompareScreenshot() {
352
394
  const tool = this._mcp.registerTool('compare_screenshot', {
353
395
  description: 'Compare a fixture\'s screenshot across two sessions (e.g. baseline vs current)',
354
- inputSchema: {
396
+ inputSchema: z.strictObject({
355
397
  fixtureId: z.string().describe('The fixture ID'),
356
398
  baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
357
399
  currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
358
400
  baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
359
401
  currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
360
- },
402
+ }),
361
403
  annotations: { readOnlyHint: true },
362
404
  }, async (args) => this._withDaemon(async (daemon) => {
363
405
  const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
@@ -391,8 +433,8 @@ class ComponentExplorerMcpServer extends Disposable {
391
433
  if (r.baselineEvents && r.baselineEvents.length > 0) {
392
434
  info.baselineEvents = r.baselineEvents;
393
435
  }
394
- if (r.baselineResultData !== undefined) {
395
- info.baselineResultData = r.baselineResultData;
436
+ if (r.baselineOutput !== undefined) {
437
+ info.baselineOutput = r.baselineOutput;
396
438
  }
397
439
  if (r.currentHasError) {
398
440
  info.currentHasError = true;
@@ -403,8 +445,8 @@ class ComponentExplorerMcpServer extends Disposable {
403
445
  if (r.currentEvents && r.currentEvents.length > 0) {
404
446
  info.currentEvents = r.currentEvents;
405
447
  }
406
- if (r.currentResultData !== undefined) {
407
- info.currentResultData = r.currentResultData;
448
+ if (r.currentOutput !== undefined) {
449
+ info.currentOutput = r.currentOutput;
408
450
  }
409
451
  if (r.approval) {
410
452
  info.approval = r.approval;
@@ -427,12 +469,12 @@ class ComponentExplorerMcpServer extends Disposable {
427
469
  _registerApproveDiff() {
428
470
  const tool = this._mcp.registerTool('approve_diff', {
429
471
  description: 'Approve a visual diff so it won\'t require re-inspection next time',
430
- inputSchema: {
472
+ inputSchema: z.strictObject({
431
473
  fixtureId: z.string(),
432
474
  originalHash: z.string(),
433
475
  modifiedHash: z.string(),
434
476
  comment: z.string().describe('Reason for approving this diff'),
435
- },
477
+ }),
436
478
  }, async (args) => this._withDaemon(async (daemon) => {
437
479
  await daemon.methods.approvals.approve(args);
438
480
  return {
@@ -450,14 +492,14 @@ class ComponentExplorerMcpServer extends Disposable {
450
492
  description: 'Approve or reject a fixture\'s screenshot based on its expectedVisualDescriptions. ' +
451
493
  'You must take a screenshot first and pass the resulting hash. ' +
452
494
  'On approve, caches (expectedVisualDescriptions, screenshotHash) so future runs auto-approve.',
453
- inputSchema: {
495
+ inputSchema: z.strictObject({
454
496
  fixtureId: z.string().describe('The fixture ID'),
455
497
  screenshotHash: z.string().describe('The screenshot hash (from a prior screenshot tool call)'),
456
498
  verdict: z.enum(['approve', 'reject']).describe('Whether the visual matches expectations'),
457
499
  comment: z.string().describe('Reason for the verdict'),
458
500
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
459
501
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
460
- },
502
+ }),
461
503
  }, async (args) => this._withDaemon(async (daemon) => {
462
504
  const sessionName = args.sessionName ?? this._defaultSessionName();
463
505
  this._log('debug', { type: 'tool-call', tool: 'review_visual', fixtureId: args.fixtureId, verdict: args.verdict });
@@ -485,13 +527,15 @@ class ComponentExplorerMcpServer extends Disposable {
485
527
  }
486
528
  else {
487
529
  return {
488
- content: [{ type: 'text', text: JSON.stringify({
530
+ content: [{
531
+ type: 'text', text: JSON.stringify({
489
532
  fixtureId: args.fixtureId,
490
533
  verdict: 'rejected',
491
534
  comment: args.comment,
492
535
  screenshotHash: args.screenshotHash,
493
536
  expectedVisualDescriptions: fixture.expectedVisualDescriptions,
494
- }, null, 2) }],
537
+ }, null, 2)
538
+ }],
495
539
  };
496
540
  }
497
541
  });
@@ -501,12 +545,12 @@ class ComponentExplorerMcpServer extends Disposable {
501
545
  this._mcp.registerTool('check_visuals', {
502
546
  description: 'Batch check visual review status for fixtures that have expectedVisualDescription. ' +
503
547
  'Returns lists of approved, needs-review, and no-expectation fixtures.',
504
- inputSchema: {
505
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
506
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label'),
548
+ inputSchema: z.strictObject({
549
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
550
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label'),
507
551
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
508
552
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
509
- },
553
+ }),
510
554
  annotations: { readOnlyHint: true },
511
555
  }, async (args) => this._withDaemon(async (daemon) => {
512
556
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -517,7 +561,7 @@ class ComponentExplorerMcpServer extends Disposable {
517
561
  if (listResult.loadError) {
518
562
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
519
563
  }
520
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
564
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
521
565
  if ('error' in filtered) {
522
566
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
523
567
  }
@@ -575,12 +619,12 @@ class ComponentExplorerMcpServer extends Disposable {
575
619
  'Returns the expression result as JSON. The expression can return a Promise (it will be awaited). ' +
576
620
  'Use this to inspect DOM state, computed styles, element dimensions, or component output. ' +
577
621
  'Do NOT use this to modify the DOM — this tool is for read-only inspection and debugging only.',
578
- inputSchema: {
622
+ inputSchema: z.strictObject({
579
623
  expression: z.string().describe('JavaScript expression to evaluate. Can return a Promise. The result must be JSON-serializable.'),
580
624
  fixtureId: z.string().optional().describe('If provided, renders this fixture before evaluating the expression'),
581
625
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
582
626
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
583
- },
627
+ }),
584
628
  }, async (args) => this._withDaemon(async (daemon) => {
585
629
  const sessionName = args.sessionName ?? this._defaultSessionName();
586
630
  this._log('debug', { type: 'tool-call', tool: 'evaluate_js', sessionName, hasFixtureId: !!args.fixtureId });
@@ -612,9 +656,9 @@ class ComponentExplorerMcpServer extends Disposable {
612
656
  description: 'Force-reload the browser page used for rendering fixtures. ' +
613
657
  'Only use this as a last resort if screenshots or evaluate_js return stale/broken results ' +
614
658
  'that persist after source changes. Normal HMR updates should handle most cases automatically.',
615
- inputSchema: {
659
+ inputSchema: z.strictObject({
616
660
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
617
- },
661
+ }),
618
662
  annotations: { destructiveHint: true },
619
663
  }, async (args) => this._withDaemon(async (daemon) => {
620
664
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -632,9 +676,9 @@ class ComponentExplorerMcpServer extends Disposable {
632
676
  _registerWatchAdd() {
633
677
  this._mcp.registerTool('watch_add', {
634
678
  description: 'Add fixtures to the watch list. Watched fixtures are automatically re-screenshotted when source changes.',
635
- inputSchema: {
679
+ inputSchema: z.strictObject({
636
680
  fixtureIds: z.array(z.string()).describe('Fixture IDs to add'),
637
- },
681
+ }),
638
682
  }, async (args) => {
639
683
  this._watchList.add(args.fixtureIds);
640
684
  return {
@@ -648,9 +692,9 @@ class ComponentExplorerMcpServer extends Disposable {
648
692
  _registerWatchRemove() {
649
693
  this._mcp.registerTool('watch_remove', {
650
694
  description: 'Remove fixtures from the watch list',
651
- inputSchema: {
695
+ inputSchema: z.strictObject({
652
696
  fixtureIds: z.array(z.string()).describe('Fixture IDs to remove'),
653
- },
697
+ }),
654
698
  }, async (args) => {
655
699
  this._watchList.remove(args.fixtureIds);
656
700
  return {
@@ -664,9 +708,9 @@ class ComponentExplorerMcpServer extends Disposable {
664
708
  _registerWatchSet() {
665
709
  this._mcp.registerTool('watch_set', {
666
710
  description: 'Replace the watch list entirely',
667
- inputSchema: {
711
+ inputSchema: z.strictObject({
668
712
  fixtureIds: z.array(z.string()).describe('Fixture IDs to watch'),
669
- },
713
+ }),
670
714
  }, async (args) => {
671
715
  this._watchList.set(args.fixtureIds);
672
716
  return {
@@ -680,12 +724,12 @@ class ComponentExplorerMcpServer extends Disposable {
680
724
  _registerWatchCompare() {
681
725
  const tool = this._mcp.registerTool('watch_compare', {
682
726
  description: 'Compare all watched fixtures across two sessions. Takes fresh screenshots from both sessions and reports which fixtures differ.',
683
- inputSchema: {
727
+ inputSchema: z.strictObject({
684
728
  baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
685
729
  currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
686
730
  baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
687
731
  currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
688
- },
732
+ }),
689
733
  annotations: { readOnlyHint: true },
690
734
  }, async (args) => this._withDaemon(async (daemon) => {
691
735
  const ids = [...this._watchList.fixtureIds];
@@ -750,10 +794,10 @@ class ComponentExplorerMcpServer extends Disposable {
750
794
  'Pass the sourceTreeId you already observed — resolves immediately if it already differs, ' +
751
795
  'otherwise waits for a source-change or ref-change event. ' +
752
796
  'If fixtures are on the watch list, automatically re-screenshots them and reports which changed.',
753
- inputSchema: {
797
+ inputSchema: z.strictObject({
754
798
  sourceTreeId: z.string().describe('The sourceTreeId the client currently knows about. The call resolves once the source tree differs from this value.'),
755
799
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
756
- },
800
+ }),
757
801
  annotations: { readOnlyHint: true },
758
802
  }, async (args) => this._withDaemon(async (daemon) => {
759
803
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -763,43 +807,49 @@ class ComponentExplorerMcpServer extends Disposable {
763
807
  return this._waitForUpdateResult(daemon, sessionName, currentSourceTreeId);
764
808
  }
765
809
  // Wait for an event that changes the source tree (max 5s)
766
- const events = await daemon.methods.events();
767
- const iterator = events[Symbol.asyncIterator]();
810
+ let onEvent = () => { };
811
+ const call = daemon.methods.events({}, { onMessage: event => onEvent(event) });
812
+ let timer;
768
813
  try {
769
- const deadline = Date.now() + 5000;
770
- while (true) {
771
- const remaining = deadline - Date.now();
772
- if (remaining <= 0) {
773
- return { content: [{ type: 'text', text: JSON.stringify({ timeout: true, sessionName, sourceTreeId: knownSourceTreeId }, null, 2) }] };
774
- }
775
- const timeout = new Promise(resolve => setTimeout(() => resolve('timeout'), remaining));
776
- const next = iterator.next();
777
- const result = await Promise.race([next, timeout]);
778
- if (result === 'timeout') {
779
- return { content: [{ type: 'text', text: JSON.stringify({ timeout: true, sessionName, sourceTreeId: knownSourceTreeId }, null, 2) }] };
780
- }
781
- const { value: event, done } = result;
782
- if (done || !event) {
783
- return { content: [{ type: 'text', text: 'Event stream ended.' }] };
784
- }
785
- const ev = event;
786
- if (ev.type === 'source-change' && ev.sourceTreeId) {
787
- this._updateSessionSourceTreeId(ev.sessionName, ev.sourceTreeId);
788
- }
789
- if (ev.type === 'ref-change') {
790
- const refreshResult = await Promise.race([this._refreshSessions(daemon), timeout]);
791
- if (refreshResult === 'timeout') {
792
- return { content: [{ type: 'text', text: JSON.stringify({ timeout: true, sessionName, sourceTreeId: knownSourceTreeId }, null, 2) }] };
814
+ const changed = new Promise((resolve, reject) => {
815
+ let refreshing = false;
816
+ onEvent = event => {
817
+ if (event.type === 'source-change') {
818
+ this._updateSessionSourceTreeId(event.sessionName, event.sourceTreeId);
793
819
  }
794
- }
795
- const newSourceTreeId = this._sourceTreeId(sessionName);
796
- if (newSourceTreeId && newSourceTreeId !== knownSourceTreeId) {
797
- return this._waitForUpdateResult(daemon, sessionName, newSourceTreeId);
798
- }
820
+ if (event.type === 'ref-change' && !refreshing) {
821
+ refreshing = true;
822
+ void this._refreshSessions(daemon).then(() => {
823
+ refreshing = false;
824
+ const newId = this._sourceTreeId(sessionName);
825
+ if (newId && newId !== knownSourceTreeId) {
826
+ resolve(newId);
827
+ }
828
+ }, reject);
829
+ }
830
+ const newId = this._sourceTreeId(sessionName);
831
+ if (newId && newId !== knownSourceTreeId) {
832
+ resolve(newId);
833
+ }
834
+ };
835
+ timer = setTimeout(() => resolve('timeout'), 5000);
836
+ void call.then(() => resolve('ended'), reject);
837
+ });
838
+ const result = await changed;
839
+ if (result === 'timeout') {
840
+ return { content: [{ type: 'text', text: JSON.stringify({ timeout: true, sessionName, sourceTreeId: knownSourceTreeId }, null, 2) }] };
799
841
  }
842
+ if (result === 'ended') {
843
+ return { content: [{ type: 'text', text: 'Event stream ended.' }] };
844
+ }
845
+ return this._waitForUpdateResult(daemon, sessionName, result);
800
846
  }
801
847
  finally {
802
- await iterator.return?.();
848
+ if (timer) {
849
+ clearTimeout(timer);
850
+ }
851
+ void call.cancel('wait completed').finally(() => call.dispose?.('wait completed')).catch(() => { });
852
+ void call.catch(() => { });
803
853
  }
804
854
  }));
805
855
  }
@@ -855,9 +905,9 @@ class ComponentExplorerMcpServer extends Disposable {
855
905
  this._mcp.registerTool('restart_session', {
856
906
  description: 'Restart a session by disposing its browser page and dev server, then recreating them. ' +
857
907
  'Use this when a session appears stuck (e.g. after a timeout).',
858
- inputSchema: {
908
+ inputSchema: z.strictObject({
859
909
  sessionName: z.string().optional().describe('Session name to restart (defaults to first session)'),
860
- },
910
+ }),
861
911
  annotations: { destructiveHint: true },
862
912
  }, async (args) => this._withDaemon(async (daemon) => {
863
913
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -875,10 +925,10 @@ class ComponentExplorerMcpServer extends Disposable {
875
925
  'The ref can be a branch name, tag, commit SHA, or the special value "INDEX" to snapshot staged changes. ' +
876
926
  'The daemon allocates a reusable worktree slot from a fixed pool (max configured in component-explorer.json). ' +
877
927
  'Returns the updated session list on success.',
878
- inputSchema: {
928
+ inputSchema: z.strictObject({
879
929
  name: z.string().describe('Unique session name (e.g. "baseline", "bisect")'),
880
930
  ref: z.string().describe('Git ref: branch, tag, commit SHA, or "INDEX" for staged changes'),
881
- },
931
+ }),
882
932
  }, async (args) => this._withDaemon(async (daemon) => {
883
933
  this._log('info', { type: 'tool-call', tool: 'open_session', name: args.name, ref: args.ref });
884
934
  const result = await daemon.methods.openSession({ name: args.name, ref: args.ref });
@@ -896,9 +946,9 @@ class ComponentExplorerMcpServer extends Disposable {
896
946
  this._mcp.registerTool('close_session', {
897
947
  description: 'Close a dynamic worktree session and release its worktree slot back to the pool. ' +
898
948
  'Cannot close static sessions configured in component-explorer.json.',
899
- inputSchema: {
949
+ inputSchema: z.strictObject({
900
950
  name: z.string().describe('Session name to close'),
901
- },
951
+ }),
902
952
  annotations: { destructiveHint: true },
903
953
  }, async (args) => this._withDaemon(async (daemon) => {
904
954
  this._log('info', { type: 'tool-call', tool: 'close_session', name: args.name });
@@ -919,10 +969,10 @@ class ComponentExplorerMcpServer extends Disposable {
919
969
  'The worktree is checked out to the new ref and Vite\'s HMR handles the incremental update (no server restart). ' +
920
970
  'Fails if the worktree has uncommitted changes — the error will list the dirty files. ' +
921
971
  'The ref can be a branch, tag, commit SHA, or "INDEX" for staged changes.',
922
- inputSchema: {
972
+ inputSchema: z.strictObject({
923
973
  name: z.string().describe('Session name to update'),
924
974
  ref: z.string().describe('New git ref: branch, tag, commit SHA, or "INDEX"'),
925
- },
975
+ }),
926
976
  }, async (args) => this._withDaemon(async (daemon) => {
927
977
  this._log('info', { type: 'tool-call', tool: 'update_session_ref', name: args.name, ref: args.ref });
928
978
  const result = await daemon.methods.updateSessionRef({ name: args.name, ref: args.ref });
@@ -939,11 +989,11 @@ class ComponentExplorerMcpServer extends Disposable {
939
989
  this._mcp.registerTool('get_url', {
940
990
  description: 'Get URL for viewing fixtures. Returns the full Component Explorer UI by default. ' +
941
991
  'Use `embedded: true` for a minimal single-fixture view (requires fixtureId).',
942
- inputSchema: {
992
+ inputSchema: z.strictObject({
943
993
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
944
994
  fixtureId: z.string().optional().describe('Specific fixture ID to view. In explorer mode, pre-selects this fixture. In embedded mode (required), shows only this fixture.'),
945
995
  embedded: z.boolean().optional().describe('If true, returns an embedded single-fixture URL (minimal UI, requires fixtureId). Default: false (full explorer UI).'),
946
- },
996
+ }),
947
997
  annotations: { readOnlyHint: true },
948
998
  }, async (args) => this._withDaemon(async (daemon) => {
949
999
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -984,6 +1034,7 @@ class ComponentExplorerMcpServer extends Disposable {
984
1034
  const mode = args.embedded ? 'embedded' : undefined;
985
1035
  const url = buildExplorerUrl({
986
1036
  baseUrl,
1037
+ pathname: EXPLORER_ROUTE,
987
1038
  mode,
988
1039
  fixtureId: args.fixtureId,
989
1040
  });
@@ -1002,32 +1053,45 @@ class ComponentExplorerMcpServer extends Disposable {
1002
1053
  }
1003
1054
  _registerCheckFixtureErrors() {
1004
1055
  this._mcp.registerTool('check_fixture_errors', {
1005
- description: 'Render fixtures and check for errors. Returns each fixture\'s error status. ' +
1056
+ description: 'Render fixtures and check for errors. Each fixture is rendered and then disposed; ' +
1057
+ 'render exceptions appear as `error`, dispose exceptions as `disposeError`, and console / window ' +
1058
+ 'events from both phases are collected in `events` (each tagged with `phase: "render" | "dispose"`). ' +
1059
+ 'A fixture is considered errored if either the render or the dispose phase reports `hasError` ' +
1060
+ '(i.e. an exception was thrown or an event of type `console.error` / `window.error` / `window.unhandledrejection` was captured). ' +
1061
+ 'Only errored fixtures are listed; successful fixtures are summarised via the `summary` counts. ' +
1006
1062
  'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task.',
1007
- inputSchema: {
1008
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1009
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1063
+ inputSchema: z.strictObject({
1064
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1065
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1010
1066
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
1011
1067
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
1012
- },
1068
+ reloadBetweenFixtures: z.boolean().optional().describe('If true, reload the browser page between fixtures. Provides cleaner isolation but is slower. ' +
1069
+ 'Default: false (the page is only reloaded after a fixture errors).'),
1070
+ input: z.unknown().optional().describe('Arbitrary JSON object passed to every fixture as `RenderContext.input`. ' +
1071
+ 'See `screenshot` for details.'),
1072
+ }),
1013
1073
  annotations: { readOnlyHint: true },
1014
1074
  }, async (args) => this._withDaemon(async (daemon) => {
1015
1075
  const sessionName = args.sessionName ?? this._defaultSessionName();
1016
- this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
1076
+ this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
1017
1077
  return this._withSourceTreeRetry(daemon, async () => {
1018
1078
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
1019
1079
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
1020
1080
  if (listResult.loadError) {
1021
1081
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
1022
1082
  }
1023
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
1083
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
1024
1084
  if ('error' in filtered) {
1025
1085
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
1026
1086
  }
1027
1087
  const fixtures = filtered.fixtures;
1028
1088
  const task = this._taskManager.startTask(async (report, signal) => {
1029
- const results = [];
1030
- report({ completed: 0, total: fixtures.length, partialResult: results });
1089
+ // Only errored fixtures are stored/reported; successful fixtures would just bloat the response.
1090
+ // The summary below still surfaces the total/ok/errored counts.
1091
+ const erroredResults = [];
1092
+ let okCount = 0;
1093
+ let processed = 0;
1094
+ report({ completed: 0, total: fixtures.length, partialResult: erroredResults });
1031
1095
  for (let i = 0; i < fixtures.length; i++) {
1032
1096
  if (signal.aborted) {
1033
1097
  break;
@@ -1039,30 +1103,52 @@ class ComponentExplorerMcpServer extends Disposable {
1039
1103
  sessionName,
1040
1104
  sourceTreeId,
1041
1105
  includeImage: false,
1106
+ // Skip the reload before the very first fixture — the page is already fresh.
1107
+ reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
1108
+ input: args.input,
1109
+ // Always dispose after rendering so teardown errors / events are observable for every fixture
1110
+ // (including the last one). Because we explicitly dispose here, the implicit
1111
+ // `previousDispose` at the start of the next render is a no-op and need not be inspected.
1112
+ disposeAfter: true,
1042
1113
  });
1043
1114
  const r = result;
1044
- const entry = { fixtureId: fixture.fixtureId, hasError: r.hasError };
1045
- if (r.error) {
1046
- entry.error = r.error;
1115
+ const renderEvents = (r.events ?? []).map(e => ({ phase: 'render', ...e }));
1116
+ const disposeEvents = (r.currentDispose?.events ?? []).map(e => ({ phase: 'dispose', ...e }));
1117
+ const events = [...renderEvents, ...disposeEvents];
1118
+ const disposeError = r.currentDispose?.errors[0];
1119
+ // Trust the upstream `hasError` flags so this stays in sync with the render-report rules
1120
+ // (exception OR an event of type console.error / window.error / window.unhandledrejection).
1121
+ const hasError = r.hasError || !!r.currentDispose?.hasError;
1122
+ if (hasError) {
1123
+ const entry = { fixtureId: fixture.fixtureId, hasError: true };
1124
+ if (r.error) {
1125
+ entry.error = r.error;
1126
+ }
1127
+ if (disposeError) {
1128
+ entry.disposeError = disposeError;
1129
+ }
1130
+ if (events.length > 0) {
1131
+ entry.events = events;
1132
+ }
1133
+ erroredResults.push(entry);
1047
1134
  }
1048
- if (r.events && r.events.length > 0) {
1049
- entry.events = r.events;
1135
+ else {
1136
+ okCount++;
1050
1137
  }
1051
- results.push(entry);
1052
1138
  }
1053
1139
  catch (e) {
1054
- results.push({
1140
+ erroredResults.push({
1055
1141
  fixtureId: fixture.fixtureId,
1056
1142
  hasError: true,
1057
1143
  error: { message: e instanceof Error ? e.message : String(e) },
1058
1144
  });
1059
1145
  }
1060
- report({ completed: i + 1, total: fixtures.length, partialResult: results });
1146
+ processed++;
1147
+ report({ completed: i + 1, total: fixtures.length, partialResult: erroredResults });
1061
1148
  }
1062
- const withErrors = results.filter(r => r.hasError);
1063
1149
  return {
1064
- fixtures: results,
1065
- summary: { total: results.length, ok: results.length - withErrors.length, errored: withErrors.length },
1150
+ fixtures: erroredResults,
1151
+ summary: { total: processed, ok: okCount, errored: erroredResults.length },
1066
1152
  };
1067
1153
  });
1068
1154
  const waited = await this._taskManager.waitForTask(task.id, 10_000);
@@ -1096,23 +1182,27 @@ class ComponentExplorerMcpServer extends Disposable {
1096
1182
  description: 'Check rendering stability of fixtures. Each fixture is unmounted, re-mounted, and screenshotted 3 times (~3s per fixture). ' +
1097
1183
  'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task. ' +
1098
1184
  'When returning a taskId, includes partial results collected so far.',
1099
- inputSchema: {
1100
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1101
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1185
+ inputSchema: z.strictObject({
1186
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1187
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1102
1188
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
1103
1189
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
1104
- },
1190
+ reloadBetweenFixtures: z.boolean().optional().describe('If true, reload the browser page between fixtures. Provides cleaner isolation but is slower. ' +
1191
+ 'Default: false (the page is only reloaded after a fixture errors).'),
1192
+ input: z.unknown().optional().describe('Arbitrary JSON object passed to every fixture as `RenderContext.input`. ' +
1193
+ 'See `screenshot` for details.'),
1194
+ }),
1105
1195
  annotations: { readOnlyHint: true },
1106
1196
  }, async (args) => this._withDaemon(async (daemon) => {
1107
1197
  const sessionName = args.sessionName ?? this._defaultSessionName();
1108
- this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
1198
+ this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
1109
1199
  return this._withSourceTreeRetry(daemon, async () => {
1110
1200
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
1111
1201
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
1112
1202
  if (listResult.loadError) {
1113
1203
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
1114
1204
  }
1115
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
1205
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
1116
1206
  if ('error' in filtered) {
1117
1207
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
1118
1208
  }
@@ -1133,6 +1223,9 @@ class ComponentExplorerMcpServer extends Disposable {
1133
1223
  sourceTreeId,
1134
1224
  includeImage: false,
1135
1225
  stabilityCheck: true,
1226
+ // Skip the reload before the very first fixture — the page is already fresh.
1227
+ reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
1228
+ input: args.input,
1136
1229
  });
1137
1230
  const r = result;
1138
1231
  results.push({
@@ -1177,9 +1270,9 @@ class ComponentExplorerMcpServer extends Disposable {
1177
1270
  _registerCheckTask() {
1178
1271
  this._mcp.registerTool('check_task', {
1179
1272
  description: 'Check on a running task. Waits up to ~2s for completion; if still running, returns progress and new results since last check.',
1180
- inputSchema: {
1273
+ inputSchema: z.strictObject({
1181
1274
  taskId: z.string().describe('The task ID returned by a previous tool call'),
1182
- },
1275
+ }),
1183
1276
  annotations: { readOnlyHint: true },
1184
1277
  }, async (args) => {
1185
1278
  const waited = await this._taskManager.waitForTask(args.taskId, 2_000);
@@ -1227,9 +1320,9 @@ class ComponentExplorerMcpServer extends Disposable {
1227
1320
  _registerCancelTask() {
1228
1321
  this._mcp.registerTool('cancel_task', {
1229
1322
  description: 'Cancel a running task',
1230
- inputSchema: {
1323
+ inputSchema: z.strictObject({
1231
1324
  taskId: z.string().describe('The task ID to cancel'),
1232
- },
1325
+ }),
1233
1326
  }, async (args) => {
1234
1327
  const task = this._taskManager.getTask(args.taskId);
1235
1328
  if (!task) {
@@ -1249,9 +1342,9 @@ class ComponentExplorerMcpServer extends Disposable {
1249
1342
  description: 'Retrieve a recently-taken screenshot image by its hash. ' +
1250
1343
  'Keeps the last ~10 images in an LRU cache. ' +
1251
1344
  'Useful for debugging when screenshot hashes behave unexpectedly.',
1252
- inputSchema: {
1345
+ inputSchema: z.strictObject({
1253
1346
  hash: z.string().describe('The screenshot hash to look up'),
1254
- },
1347
+ }),
1255
1348
  annotations: { readOnlyHint: true },
1256
1349
  }, async (args) => {
1257
1350
  const image = this._imageLru.get(args.hash);
@@ -1275,9 +1368,9 @@ class ComponentExplorerMcpServer extends Disposable {
1275
1368
  'Only use this tool when the user explicitly asks to show or hide the browser. ' +
1276
1369
  'Do not call this tool automatically or as part of other workflows. ' +
1277
1370
  'Note: changing visibility closes the current browser instance, so the next screenshot or evaluate_js call will relaunch it.',
1278
- inputSchema: {
1371
+ inputSchema: z.strictObject({
1279
1372
  visible: z.boolean().describe('true to show the browser window (headed mode), false to hide it (headless mode)'),
1280
- },
1373
+ }),
1281
1374
  annotations: { destructiveHint: true },
1282
1375
  }, async (args) => this._withDaemon(async (daemon) => {
1283
1376
  await daemon.methods.setBrowserVisibility({ visible: args.visible });