unity-open-mcp 0.1.0 → 0.3.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.
Files changed (447) hide show
  1. package/dist/agent-identity.d.ts +52 -0
  2. package/dist/agent-identity.js +115 -0
  3. package/dist/agent-identity.js.map +1 -0
  4. package/dist/batch-spawn.d.ts +7 -0
  5. package/dist/batch-spawn.js +155 -29
  6. package/dist/batch-spawn.js.map +1 -1
  7. package/dist/capabilities/build-capabilities.d.ts +45 -0
  8. package/dist/capabilities/build-capabilities.js +262 -27
  9. package/dist/capabilities/build-capabilities.js.map +1 -1
  10. package/dist/capabilities/cost-hints.d.ts +82 -0
  11. package/dist/capabilities/cost-hints.js +185 -0
  12. package/dist/capabilities/cost-hints.js.map +1 -0
  13. package/dist/capabilities/lifecycle.d.ts +40 -0
  14. package/dist/capabilities/lifecycle.js +353 -0
  15. package/dist/capabilities/lifecycle.js.map +1 -0
  16. package/dist/capabilities/rule-catalog.d.ts +13 -1
  17. package/dist/capabilities/rule-catalog.js +620 -75
  18. package/dist/capabilities/rule-catalog.js.map +1 -1
  19. package/dist/capabilities/tool-groups.d.ts +21 -0
  20. package/dist/capabilities/tool-groups.js +411 -14
  21. package/dist/capabilities/tool-groups.js.map +1 -1
  22. package/dist/cli/args.d.ts +32 -1
  23. package/dist/cli/args.js +201 -4
  24. package/dist/cli/args.js.map +1 -1
  25. package/dist/cli/cli.d.ts +21 -0
  26. package/dist/cli/cli.js +68 -5
  27. package/dist/cli/cli.js.map +1 -1
  28. package/dist/cli/commands.d.ts +33 -0
  29. package/dist/cli/commands.js +400 -0
  30. package/dist/cli/commands.js.map +1 -1
  31. package/dist/cli/exit-codes.d.ts +43 -0
  32. package/dist/cli/exit-codes.js +78 -0
  33. package/dist/cli/exit-codes.js.map +1 -0
  34. package/dist/compile-verify.d.ts +65 -0
  35. package/dist/compile-verify.js +94 -0
  36. package/dist/compile-verify.js.map +1 -0
  37. package/dist/compressible-router.js +64 -11
  38. package/dist/compressible-router.js.map +1 -1
  39. package/dist/compression/asset-model.d.ts +15 -0
  40. package/dist/compression/compact.d.ts +5 -4
  41. package/dist/compression/compact.js +13 -10
  42. package/dist/compression/compact.js.map +1 -1
  43. package/dist/dialog-dismiss.d.ts +127 -74
  44. package/dist/dialog-dismiss.js +427 -170
  45. package/dist/dialog-dismiss.js.map +1 -1
  46. package/dist/dialog-policy.d.ts +141 -0
  47. package/dist/dialog-policy.js +345 -0
  48. package/dist/dialog-policy.js.map +1 -0
  49. package/dist/gate-error.d.ts +6 -0
  50. package/dist/gate-error.js +13 -2
  51. package/dist/gate-error.js.map +1 -1
  52. package/dist/hub-control.d.ts +247 -0
  53. package/dist/hub-control.js +703 -0
  54. package/dist/hub-control.js.map +1 -0
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.js +46 -12
  57. package/dist/index.js.map +1 -1
  58. package/dist/live-client.d.ts +123 -3
  59. package/dist/live-client.js +526 -69
  60. package/dist/live-client.js.map +1 -1
  61. package/dist/offline.d.ts +105 -0
  62. package/dist/offline.js +816 -23
  63. package/dist/offline.js.map +1 -1
  64. package/dist/output-profile.d.ts +110 -0
  65. package/dist/output-profile.js +255 -0
  66. package/dist/output-profile.js.map +1 -0
  67. package/dist/retry-policy.d.ts +72 -0
  68. package/dist/retry-policy.js +146 -0
  69. package/dist/retry-policy.js.map +1 -0
  70. package/dist/routers.d.ts +4 -0
  71. package/dist/routers.js +2 -2
  72. package/dist/routers.js.map +1 -1
  73. package/dist/server-instructions.d.ts +18 -0
  74. package/dist/server-instructions.js +145 -0
  75. package/dist/server-instructions.js.map +1 -0
  76. package/dist/tool-router.d.ts +38 -1
  77. package/dist/tool-router.js +690 -37
  78. package/dist/tool-router.js.map +1 -1
  79. package/dist/tool-session-state.d.ts +37 -0
  80. package/dist/tool-session-state.js +78 -1
  81. package/dist/tool-session-state.js.map +1 -1
  82. package/dist/tools/apply-fix.js +16 -2
  83. package/dist/tools/apply-fix.js.map +1 -1
  84. package/dist/tools/asmdef-create.d.ts +2 -0
  85. package/dist/tools/asmdef-create.js +114 -0
  86. package/dist/tools/asmdef-create.js.map +1 -0
  87. package/dist/tools/asmdef-get.d.ts +2 -0
  88. package/dist/tools/asmdef-get.js +31 -0
  89. package/dist/tools/asmdef-get.js.map +1 -0
  90. package/dist/tools/asmdef-list.d.ts +2 -0
  91. package/dist/tools/asmdef-list.js +41 -0
  92. package/dist/tools/asmdef-list.js.map +1 -0
  93. package/dist/tools/asmdef-modify.d.ts +2 -0
  94. package/dist/tools/asmdef-modify.js +108 -0
  95. package/dist/tools/asmdef-modify.js.map +1 -0
  96. package/dist/tools/audio-listener-get.d.ts +2 -0
  97. package/dist/tools/audio-listener-get.js +16 -0
  98. package/dist/tools/audio-listener-get.js.map +1 -0
  99. package/dist/tools/audio-mixer-get-parameter.d.ts +2 -0
  100. package/dist/tools/audio-mixer-get-parameter.js +25 -0
  101. package/dist/tools/audio-mixer-get-parameter.js.map +1 -0
  102. package/dist/tools/audio-mixer-set-parameter.d.ts +2 -0
  103. package/dist/tools/audio-mixer-set-parameter.js +44 -0
  104. package/dist/tools/audio-mixer-set-parameter.js.map +1 -0
  105. package/dist/tools/audio-source-add.d.ts +2 -0
  106. package/dist/tools/audio-source-add.js +94 -0
  107. package/dist/tools/audio-source-add.js.map +1 -0
  108. package/dist/tools/audio-source-modify.d.ts +2 -0
  109. package/dist/tools/audio-source-modify.js +52 -0
  110. package/dist/tools/audio-source-modify.js.map +1 -0
  111. package/dist/tools/bridge-status.js +16 -6
  112. package/dist/tools/bridge-status.js.map +1 -1
  113. package/dist/tools/build-get-active-target.js +1 -2
  114. package/dist/tools/build-get-active-target.js.map +1 -1
  115. package/dist/tools/build-get-defines.js +1 -1
  116. package/dist/tools/build-get-defines.js.map +1 -1
  117. package/dist/tools/build-get-scenes.js +1 -2
  118. package/dist/tools/build-get-scenes.js.map +1 -1
  119. package/dist/tools/build-get-targets.js +1 -1
  120. package/dist/tools/build-get-targets.js.map +1 -1
  121. package/dist/tools/build-set-defines.js +1 -2
  122. package/dist/tools/build-set-defines.js.map +1 -1
  123. package/dist/tools/build-set-scenes.js +1 -1
  124. package/dist/tools/build-set-scenes.js.map +1 -1
  125. package/dist/tools/build-set-target.js +1 -1
  126. package/dist/tools/build-set-target.js.map +1 -1
  127. package/dist/tools/build-start.js +0 -1
  128. package/dist/tools/build-start.js.map +1 -1
  129. package/dist/tools/cinemachine-brain-ensure.d.ts +2 -0
  130. package/dist/tools/cinemachine-brain-ensure.js +27 -0
  131. package/dist/tools/cinemachine-brain-ensure.js.map +1 -0
  132. package/dist/tools/cinemachine-camera-list.d.ts +2 -0
  133. package/dist/tools/cinemachine-camera-list.js +15 -0
  134. package/dist/tools/cinemachine-camera-list.js.map +1 -0
  135. package/dist/tools/cinemachine-create-camera.d.ts +2 -0
  136. package/dist/tools/cinemachine-create-camera.js +55 -0
  137. package/dist/tools/cinemachine-create-camera.js.map +1 -0
  138. package/dist/tools/cinemachine-set-body.d.ts +2 -0
  139. package/dist/tools/cinemachine-set-body.js +33 -0
  140. package/dist/tools/cinemachine-set-body.js.map +1 -0
  141. package/dist/tools/cinemachine-set-lens.d.ts +2 -0
  142. package/dist/tools/cinemachine-set-lens.js +41 -0
  143. package/dist/tools/cinemachine-set-lens.js.map +1 -0
  144. package/dist/tools/cinemachine-set-noise.d.ts +2 -0
  145. package/dist/tools/cinemachine-set-noise.js +31 -0
  146. package/dist/tools/cinemachine-set-noise.js.map +1 -0
  147. package/dist/tools/cinemachine-set-targets.d.ts +2 -0
  148. package/dist/tools/cinemachine-set-targets.js +30 -0
  149. package/dist/tools/cinemachine-set-targets.js.map +1 -0
  150. package/dist/tools/console-log.js +2 -2
  151. package/dist/tools/console-log.js.map +1 -1
  152. package/dist/tools/constraint-add.d.ts +2 -0
  153. package/dist/tools/constraint-add.js +85 -0
  154. package/dist/tools/constraint-add.js.map +1 -0
  155. package/dist/tools/dependencies.d.ts +2 -0
  156. package/dist/tools/dependencies.js +76 -0
  157. package/dist/tools/dependencies.js.map +1 -0
  158. package/dist/tools/editor-add-tag.js +2 -3
  159. package/dist/tools/editor-add-tag.js.map +1 -1
  160. package/dist/tools/editor-clear-history.d.ts +2 -0
  161. package/dist/tools/editor-clear-history.js +25 -0
  162. package/dist/tools/editor-clear-history.js.map +1 -0
  163. package/dist/tools/editor-get-layers.js +3 -3
  164. package/dist/tools/editor-get-layers.js.map +1 -1
  165. package/dist/tools/editor-get-tags.js +2 -2
  166. package/dist/tools/editor-get-tags.js.map +1 -1
  167. package/dist/tools/editor-redo.js +2 -3
  168. package/dist/tools/editor-redo.js.map +1 -1
  169. package/dist/tools/editor-undo-history.d.ts +2 -0
  170. package/dist/tools/editor-undo-history.js +21 -0
  171. package/dist/tools/editor-undo-history.js.map +1 -0
  172. package/dist/tools/editor-undo.js +3 -3
  173. package/dist/tools/editor-undo.js.map +1 -1
  174. package/dist/tools/editorprefs-delete.d.ts +2 -0
  175. package/dist/tools/editorprefs-delete.js +23 -0
  176. package/dist/tools/editorprefs-delete.js.map +1 -0
  177. package/dist/tools/editorprefs-get.d.ts +2 -0
  178. package/dist/tools/editorprefs-get.js +28 -0
  179. package/dist/tools/editorprefs-get.js.map +1 -0
  180. package/dist/tools/editorprefs-set.d.ts +2 -0
  181. package/dist/tools/editorprefs-set.js +33 -0
  182. package/dist/tools/editorprefs-set.js.map +1 -0
  183. package/dist/tools/find-references.js +24 -4
  184. package/dist/tools/find-references.js.map +1 -1
  185. package/dist/tools/frame-debugger.d.ts +2 -0
  186. package/dist/tools/frame-debugger.js +48 -0
  187. package/dist/tools/frame-debugger.js.map +1 -0
  188. package/dist/tools/gameobject-modify.js +69 -2
  189. package/dist/tools/gameobject-modify.js.map +1 -1
  190. package/dist/tools/gate-budget-estimate.js +2 -1
  191. package/dist/tools/gate-budget-estimate.js.map +1 -1
  192. package/dist/tools/hub-available-releases.d.ts +2 -0
  193. package/dist/tools/hub-available-releases.js +23 -0
  194. package/dist/tools/hub-available-releases.js.map +1 -0
  195. package/dist/tools/hub-get-install-path.d.ts +2 -0
  196. package/dist/tools/hub-get-install-path.js +20 -0
  197. package/dist/tools/hub-get-install-path.js.map +1 -0
  198. package/dist/tools/hub-install-editor.d.ts +2 -0
  199. package/dist/tools/hub-install-editor.js +47 -0
  200. package/dist/tools/hub-install-editor.js.map +1 -0
  201. package/dist/tools/hub-install-modules.d.ts +2 -0
  202. package/dist/tools/hub-install-modules.js +44 -0
  203. package/dist/tools/hub-install-modules.js.map +1 -0
  204. package/dist/tools/hub-list-editors.d.ts +2 -0
  205. package/dist/tools/hub-list-editors.js +25 -0
  206. package/dist/tools/hub-list-editors.js.map +1 -0
  207. package/dist/tools/hub-set-install-path.d.ts +2 -0
  208. package/dist/tools/hub-set-install-path.js +30 -0
  209. package/dist/tools/hub-set-install-path.js.map +1 -0
  210. package/dist/tools/impact-preview.js +2 -2
  211. package/dist/tools/impact-preview.js.map +1 -1
  212. package/dist/tools/index.d.ts +14 -0
  213. package/dist/tools/index.js +534 -24
  214. package/dist/tools/index.js.map +1 -1
  215. package/dist/tools/light-add.d.ts +2 -0
  216. package/dist/tools/light-add.js +73 -0
  217. package/dist/tools/light-add.js.map +1 -0
  218. package/dist/tools/light-modify.d.ts +2 -0
  219. package/dist/tools/light-modify.js +36 -0
  220. package/dist/tools/light-modify.js.map +1 -0
  221. package/dist/tools/light-set.d.ts +2 -0
  222. package/dist/tools/light-set.js +54 -0
  223. package/dist/tools/light-set.js.map +1 -0
  224. package/dist/tools/list-assets-of-type.d.ts +2 -0
  225. package/dist/tools/list-assets-of-type.js +54 -0
  226. package/dist/tools/list-assets-of-type.js.map +1 -0
  227. package/dist/tools/lod-add-level.d.ts +2 -0
  228. package/dist/tools/lod-add-level.js +66 -0
  229. package/dist/tools/lod-add-level.js.map +1 -0
  230. package/dist/tools/lod-group-configure.d.ts +2 -0
  231. package/dist/tools/lod-group-configure.js +64 -0
  232. package/dist/tools/lod-group-configure.js.map +1 -0
  233. package/dist/tools/manage-tools.js +7 -5
  234. package/dist/tools/manage-tools.js.map +1 -1
  235. package/dist/tools/memory-snapshot-capture.d.ts +2 -0
  236. package/dist/tools/memory-snapshot-capture.js +61 -0
  237. package/dist/tools/memory-snapshot-capture.js.map +1 -0
  238. package/dist/tools/mutation-explain.js +2 -2
  239. package/dist/tools/mutation-explain.js.map +1 -1
  240. package/dist/tools/playerprefs-delete.d.ts +2 -0
  241. package/dist/tools/playerprefs-delete.js +24 -0
  242. package/dist/tools/playerprefs-delete.js.map +1 -0
  243. package/dist/tools/playerprefs-get.d.ts +2 -0
  244. package/dist/tools/playerprefs-get.js +28 -0
  245. package/dist/tools/playerprefs-get.js.map +1 -0
  246. package/dist/tools/playerprefs-set.d.ts +2 -0
  247. package/dist/tools/playerprefs-set.js +37 -0
  248. package/dist/tools/playerprefs-set.js.map +1 -0
  249. package/dist/tools/profiler-capture-frame.d.ts +2 -0
  250. package/dist/tools/profiler-capture-frame.js +75 -0
  251. package/dist/tools/profiler-capture-frame.js.map +1 -0
  252. package/dist/tools/profiler-clear-data.js +1 -2
  253. package/dist/tools/profiler-clear-data.js.map +1 -1
  254. package/dist/tools/profiler-enable-module.js +4 -5
  255. package/dist/tools/profiler-enable-module.js.map +1 -1
  256. package/dist/tools/profiler-get-status.js +5 -6
  257. package/dist/tools/profiler-get-status.js.map +1 -1
  258. package/dist/tools/profiler-load-data.js +4 -5
  259. package/dist/tools/profiler-load-data.js.map +1 -1
  260. package/dist/tools/profiler-save-data.js +3 -3
  261. package/dist/tools/profiler-save-data.js.map +1 -1
  262. package/dist/tools/profiler-set-config.js +5 -5
  263. package/dist/tools/profiler-set-config.js.map +1 -1
  264. package/dist/tools/profiler-start.js +4 -4
  265. package/dist/tools/profiler-start.js.map +1 -1
  266. package/dist/tools/profiler-stop.js +2 -3
  267. package/dist/tools/profiler-stop.js.map +1 -1
  268. package/dist/tools/read-asset.js +21 -3
  269. package/dist/tools/read-asset.js.map +1 -1
  270. package/dist/tools/reflection-probe-bake.d.ts +2 -0
  271. package/dist/tools/reflection-probe-bake.js +47 -0
  272. package/dist/tools/reflection-probe-bake.js.map +1 -0
  273. package/dist/tools/reflection-probe-get.d.ts +2 -0
  274. package/dist/tools/reflection-probe-get.js +19 -0
  275. package/dist/tools/reflection-probe-get.js.map +1 -0
  276. package/dist/tools/reimport-package.d.ts +2 -0
  277. package/dist/tools/reimport-package.js +53 -0
  278. package/dist/tools/reimport-package.js.map +1 -0
  279. package/dist/tools/scan-paths.js +19 -0
  280. package/dist/tools/scan-paths.js.map +1 -1
  281. package/dist/tools/scene-focus.js +1 -1
  282. package/dist/tools/scene-focus.js.map +1 -1
  283. package/dist/tools/scene-get-data.js +31 -13
  284. package/dist/tools/scene-get-data.js.map +1 -1
  285. package/dist/tools/scene-get-dirty-summary.js +1 -1
  286. package/dist/tools/scene-get-dirty-summary.js.map +1 -1
  287. package/dist/tools/sceneview-get-camera.d.ts +2 -0
  288. package/dist/tools/sceneview-get-camera.js +16 -0
  289. package/dist/tools/sceneview-get-camera.js.map +1 -0
  290. package/dist/tools/sceneview-set-camera.d.ts +2 -0
  291. package/dist/tools/sceneview-set-camera.js +58 -0
  292. package/dist/tools/sceneview-set-camera.js.map +1 -0
  293. package/dist/tools/script-write.js +2 -2
  294. package/dist/tools/script-write.js.map +1 -1
  295. package/dist/tools/scriptableobject-create.d.ts +2 -0
  296. package/dist/tools/scriptableobject-create.js +86 -0
  297. package/dist/tools/scriptableobject-create.js.map +1 -0
  298. package/dist/tools/search-assets.js +20 -3
  299. package/dist/tools/search-assets.js.map +1 -1
  300. package/dist/tools/settings-get-lighting.js +3 -4
  301. package/dist/tools/settings-get-lighting.js.map +1 -1
  302. package/dist/tools/settings-get-player.js +2 -2
  303. package/dist/tools/settings-get-player.js.map +1 -1
  304. package/dist/tools/settings-get-quality.js +1 -2
  305. package/dist/tools/settings-get-quality.js.map +1 -1
  306. package/dist/tools/settings-get-render-pipeline.d.ts +2 -0
  307. package/dist/tools/settings-get-render-pipeline.js +19 -0
  308. package/dist/tools/settings-get-render-pipeline.js.map +1 -0
  309. package/dist/tools/settings-get-time.d.ts +2 -0
  310. package/dist/tools/settings-get-time.js +18 -0
  311. package/dist/tools/settings-get-time.js.map +1 -0
  312. package/dist/tools/settings-set-lighting.js +5 -5
  313. package/dist/tools/settings-set-lighting.js.map +1 -1
  314. package/dist/tools/settings-set-physics.js +2 -2
  315. package/dist/tools/settings-set-physics.js.map +1 -1
  316. package/dist/tools/settings-set-player.js +2 -2
  317. package/dist/tools/settings-set-player.js.map +1 -1
  318. package/dist/tools/settings-set-quality-level.d.ts +2 -0
  319. package/dist/tools/settings-set-quality-level.js +44 -0
  320. package/dist/tools/settings-set-quality-level.js.map +1 -0
  321. package/dist/tools/settings-set-quality.js +2 -2
  322. package/dist/tools/settings-set-quality.js.map +1 -1
  323. package/dist/tools/settings-set-time.d.ts +2 -0
  324. package/dist/tools/settings-set-time.js +53 -0
  325. package/dist/tools/settings-set-time.js.map +1 -0
  326. package/dist/tools/shader-get-data.js +3 -3
  327. package/dist/tools/shader-get-data.js.map +1 -1
  328. package/dist/tools/shader-graph-create.d.ts +2 -0
  329. package/dist/tools/shader-graph-create.js +40 -0
  330. package/dist/tools/shader-graph-create.js.map +1 -0
  331. package/dist/tools/shader-graph-node-add.d.ts +2 -0
  332. package/dist/tools/shader-graph-node-add.js +50 -0
  333. package/dist/tools/shader-graph-node-add.js.map +1 -0
  334. package/dist/tools/shader-graph-node-connect.d.ts +2 -0
  335. package/dist/tools/shader-graph-node-connect.js +60 -0
  336. package/dist/tools/shader-graph-node-connect.js.map +1 -0
  337. package/dist/tools/shader-graph-open.d.ts +2 -0
  338. package/dist/tools/shader-graph-open.js +29 -0
  339. package/dist/tools/shader-graph-open.js.map +1 -0
  340. package/dist/tools/skybox-get.d.ts +2 -0
  341. package/dist/tools/skybox-get.js +13 -0
  342. package/dist/tools/skybox-get.js.map +1 -0
  343. package/dist/tools/skybox-set.d.ts +2 -0
  344. package/dist/tools/skybox-set.js +34 -0
  345. package/dist/tools/skybox-set.js.map +1 -0
  346. package/dist/tools/spriteatlas-add-packable.d.ts +2 -0
  347. package/dist/tools/spriteatlas-add-packable.js +40 -0
  348. package/dist/tools/spriteatlas-add-packable.js.map +1 -0
  349. package/dist/tools/spriteatlas-create.d.ts +2 -0
  350. package/dist/tools/spriteatlas-create.js +41 -0
  351. package/dist/tools/spriteatlas-create.js.map +1 -0
  352. package/dist/tools/spriteatlas-delete.d.ts +2 -0
  353. package/dist/tools/spriteatlas-delete.js +32 -0
  354. package/dist/tools/spriteatlas-delete.js.map +1 -0
  355. package/dist/tools/spriteatlas-get.d.ts +2 -0
  356. package/dist/tools/spriteatlas-get.js +23 -0
  357. package/dist/tools/spriteatlas-get.js.map +1 -0
  358. package/dist/tools/spriteatlas-list.d.ts +2 -0
  359. package/dist/tools/spriteatlas-list.js +21 -0
  360. package/dist/tools/spriteatlas-list.js.map +1 -0
  361. package/dist/tools/spriteatlas-modify.d.ts +2 -0
  362. package/dist/tools/spriteatlas-modify.js +55 -0
  363. package/dist/tools/spriteatlas-modify.js.map +1 -0
  364. package/dist/tools/spriteatlas-remove-packable.d.ts +2 -0
  365. package/dist/tools/spriteatlas-remove-packable.js +39 -0
  366. package/dist/tools/spriteatlas-remove-packable.js.map +1 -0
  367. package/dist/tools/terrain-create.d.ts +2 -0
  368. package/dist/tools/terrain-create.js +73 -0
  369. package/dist/tools/terrain-create.js.map +1 -0
  370. package/dist/tools/terrain-paint-layer.d.ts +2 -0
  371. package/dist/tools/terrain-paint-layer.js +85 -0
  372. package/dist/tools/terrain-paint-layer.js.map +1 -0
  373. package/dist/tools/terrain-place-trees.d.ts +2 -0
  374. package/dist/tools/terrain-place-trees.js +92 -0
  375. package/dist/tools/terrain-place-trees.js.map +1 -0
  376. package/dist/tools/terrain-set-heights.d.ts +2 -0
  377. package/dist/tools/terrain-set-heights.js +68 -0
  378. package/dist/tools/terrain-set-heights.js.map +1 -0
  379. package/dist/tools/terrain-set-neighbors.d.ts +2 -0
  380. package/dist/tools/terrain-set-neighbors.js +65 -0
  381. package/dist/tools/terrain-set-neighbors.js.map +1 -0
  382. package/dist/tools/texture-get-importer.d.ts +2 -0
  383. package/dist/tools/texture-get-importer.js +25 -0
  384. package/dist/tools/texture-get-importer.js.map +1 -0
  385. package/dist/tools/texture-get.d.ts +2 -0
  386. package/dist/tools/texture-get.js +23 -0
  387. package/dist/tools/texture-get.js.map +1 -0
  388. package/dist/tools/texture-reimport.d.ts +2 -0
  389. package/dist/tools/texture-reimport.js +33 -0
  390. package/dist/tools/texture-reimport.js.map +1 -0
  391. package/dist/tools/texture-set-import.d.ts +2 -0
  392. package/dist/tools/texture-set-import.js +52 -0
  393. package/dist/tools/texture-set-import.js.map +1 -0
  394. package/dist/tools/tilemap-box-fill.d.ts +2 -0
  395. package/dist/tools/tilemap-box-fill.js +32 -0
  396. package/dist/tools/tilemap-box-fill.js.map +1 -0
  397. package/dist/tools/tilemap-create-rule-tile.d.ts +2 -0
  398. package/dist/tools/tilemap-create-rule-tile.js +39 -0
  399. package/dist/tools/tilemap-create-rule-tile.js.map +1 -0
  400. package/dist/tools/tilemap-create-tile-asset.d.ts +2 -0
  401. package/dist/tools/tilemap-create-tile-asset.js +35 -0
  402. package/dist/tools/tilemap-create-tile-asset.js.map +1 -0
  403. package/dist/tools/tilemap-create.d.ts +2 -0
  404. package/dist/tools/tilemap-create.js +32 -0
  405. package/dist/tools/tilemap-create.js.map +1 -0
  406. package/dist/tools/tilemap-set-tile.d.ts +2 -0
  407. package/dist/tools/tilemap-set-tile.js +33 -0
  408. package/dist/tools/tilemap-set-tile.js.map +1 -0
  409. package/dist/tools/timeline-clip-add.d.ts +2 -0
  410. package/dist/tools/timeline-clip-add.js +43 -0
  411. package/dist/tools/timeline-clip-add.js.map +1 -0
  412. package/dist/tools/timeline-create.d.ts +2 -0
  413. package/dist/tools/timeline-create.js +32 -0
  414. package/dist/tools/timeline-create.js.map +1 -0
  415. package/dist/tools/timeline-director-bind.d.ts +2 -0
  416. package/dist/tools/timeline-director-bind.js +34 -0
  417. package/dist/tools/timeline-director-bind.js.map +1 -0
  418. package/dist/tools/timeline-modify.d.ts +2 -0
  419. package/dist/tools/timeline-modify.js +44 -0
  420. package/dist/tools/timeline-modify.js.map +1 -0
  421. package/dist/tools/timeline-track-add.d.ts +2 -0
  422. package/dist/tools/timeline-track-add.js +37 -0
  423. package/dist/tools/timeline-track-add.js.map +1 -0
  424. package/dist/tools/ui-canvas-add.d.ts +2 -0
  425. package/dist/tools/ui-canvas-add.js +66 -0
  426. package/dist/tools/ui-canvas-add.js.map +1 -0
  427. package/dist/tools/ui-element-add.d.ts +2 -0
  428. package/dist/tools/ui-element-add.js +65 -0
  429. package/dist/tools/ui-element-add.js.map +1 -0
  430. package/dist/tools/ui-element-modify.d.ts +2 -0
  431. package/dist/tools/ui-element-modify.js +59 -0
  432. package/dist/tools/ui-element-modify.js.map +1 -0
  433. package/dist/tools/ui-layout-group-add.d.ts +2 -0
  434. package/dist/tools/ui-layout-group-add.js +80 -0
  435. package/dist/tools/ui-layout-group-add.js.map +1 -0
  436. package/dist/tools/validate-edit.js +22 -1
  437. package/dist/tools/validate-edit.js.map +1 -1
  438. package/dist/tools/vfx-block-edit.d.ts +2 -0
  439. package/dist/tools/vfx-block-edit.js +51 -0
  440. package/dist/tools/vfx-block-edit.js.map +1 -0
  441. package/dist/tools/vfx-list.d.ts +2 -0
  442. package/dist/tools/vfx-list.js +31 -0
  443. package/dist/tools/vfx-list.js.map +1 -0
  444. package/dist/tools/vfx-open.d.ts +2 -0
  445. package/dist/tools/vfx-open.js +29 -0
  446. package/dist/tools/vfx-open.js.map +1 -0
  447. package/package.json +1 -1
@@ -1,16 +1,26 @@
1
1
  import { AssetModelCache, isCompressible, routeCompressible } from "./compressible-router.js";
2
- import { listAssetsOffline, findReferencesOffline } from "./offline.js";
2
+ import { listAssetsOffline, findReferencesOffline, dependenciesOffline } from "./offline.js";
3
3
  import { editorLogPath, readLogTail, DEFAULT_LOG_TAIL_BYTES } from "./unity-log.js";
4
4
  import { summarizeProjectHealth } from "./project-health.js";
5
5
  import { buildCapabilities } from "./capabilities/build-capabilities.js";
6
6
  import { RULE_CATALOG, FIX_CATALOG } from "./capabilities/rule-catalog.js";
7
- import { GROUP_IDS, TOOL_GROUPS, groupToTools, } from "./capabilities/tool-groups.js";
7
+ import { GROUP_IDS, TOOL_GROUPS, AUTO_ACTIVATE_GROUPS, groupToTools, } from "./capabilities/tool-groups.js";
8
8
  import { listRules } from "./capabilities/list-rules.js";
9
9
  import { generateSkill } from "./skill/generate-skill.js";
10
10
  import { knownClientKeys } from "./skill/client-paths.js";
11
11
  import { ALL_TOOLS } from "./tools/index.js";
12
- import { lockPath, readInstanceLock, classifyInstance } from "./instance-discovery.js";
12
+ import { lockPath, readInstanceLock, classifyInstance, isPidAlive } from "./instance-discovery.js";
13
+ import { listInstalledEditors, fetchAvailableReleases, openInstallDeepLink, getInstallPath, setInstallPath, } from "./hub-control.js";
14
+ /** Default Hub-control backend — calls the real hub-control functions. */
15
+ const defaultHubBackend = {
16
+ listInstalledEditors: () => listInstalledEditors(),
17
+ fetchAvailableReleases: () => fetchAvailableReleases(),
18
+ openInstallDeepLink: (version, changeset) => openInstallDeepLink(version, changeset),
19
+ getInstallPath: () => getInstallPath(),
20
+ setInstallPath: (path) => setInstallPath(path),
21
+ };
13
22
  import { BATCH_TOOL_NAMES } from "./batch-spawn.js";
23
+ import { readProfileAndDetail, applyPaging, attachPagination, foldVerifyResult, parseResultBody, withResultBody, } from "./output-profile.js";
14
24
  function injectRouteMeta(result, meta) {
15
25
  if (result.content.length === 0)
16
26
  return result;
@@ -35,6 +45,43 @@ function injectRouteMeta(result, meta) {
35
45
  return result;
36
46
  }
37
47
  }
48
+ // M26 Plan 2 — local-routed error result (used by the Hub control tools for
49
+ // missing-parameter refusals before any side effect runs). Tagged _source=local
50
+ // because it is resolved entirely in the MCP server.
51
+ function localError(code, message) {
52
+ return {
53
+ content: [
54
+ { type: "text", text: JSON.stringify({ error: { code, message }, _source: "local" }) },
55
+ ],
56
+ isError: true,
57
+ };
58
+ }
59
+ // M26 Plan 2 — gate-consistent envelope shape for the mutating Hub control
60
+ // tools (install_editor / install_modules / set_install_path). These are
61
+ // system-level ops, not project-asset mutations, so paths_hint is N/A and the
62
+ // gate is skipped (gate.skipped=true). The shape matches MutationEnvelope so
63
+ // agents parse it uniformly alongside bridge-routed mutating tools.
64
+ function hubMutationEnvelope(opts) {
65
+ const body = {
66
+ mutation: {
67
+ success: opts.success,
68
+ output: opts.output,
69
+ error: opts.error,
70
+ },
71
+ gate: {
72
+ mode: "off",
73
+ skipped: true,
74
+ validation: null,
75
+ delta: null,
76
+ },
77
+ agentNextSteps: opts.nextSteps,
78
+ _source: "local",
79
+ };
80
+ return {
81
+ content: [{ type: "text", text: JSON.stringify(body) }],
82
+ isError: opts.isError,
83
+ };
84
+ }
38
85
  function activeGroupsEqual(a, b) {
39
86
  if (a.length !== b.length)
40
87
  return false;
@@ -77,6 +124,66 @@ function summarizeBridgeStatusLock(lock) {
77
124
  unityVersion: lock.unityVersion,
78
125
  };
79
126
  }
127
+ // ===========================================================================
128
+ // M22 — heavy-tool result post-processors (find_references + verify tools).
129
+ //
130
+ // These run after the live/batch/offline hop returns. The bridge emits the full
131
+ // result regardless of profile; we fold + page server-side so the wire shape
132
+ // matches the profile/paging contract without a bridge change. Each returns a
133
+ // new CallToolResult (never mutates the input); injectRouteMeta runs after to
134
+ // append `_route`.
135
+ // ===========================================================================
136
+ /** Page the offline find_references `referencedBy` list and attach pagination. */
137
+ function pageFindReferencesResult(result, pageSize, cursor) {
138
+ const referencedBy = Array.isArray(result.referencedBy) ? result.referencedBy : [];
139
+ const { page, block } = applyPaging(referencedBy, "find_references", { page_size: pageSize, cursor });
140
+ return attachPagination({ ...result, referencedBy: page }, block);
141
+ }
142
+ /**
143
+ * Fold + page the LIVE find_references result. The bridge returns a flat
144
+ * `referencedBy` path list (no byKind/byFolder groupings, no locations); we
145
+ * derive the compact grouping server-side and page when requested.
146
+ */
147
+ function foldFindReferencesLive(result, profile, wantPaging, args) {
148
+ const body = parseResultBody(result);
149
+ if (body === null)
150
+ return result;
151
+ if (body.error)
152
+ return result;
153
+ // Bridge shape: { referencedBy: string[], totalCount, ... } (flat paths).
154
+ const rawList = Array.isArray(body.referencedBy) ? body.referencedBy : [];
155
+ const pageSize = typeof args.page_size === "number" && args.page_size > 0 ? args.page_size : 0;
156
+ const cursor = typeof args.cursor === "string" ? args.cursor : undefined;
157
+ if (profile === "compact") {
158
+ // compact: counts + byKind grouping only — drop the per-asset list.
159
+ const { referencedBy: _dropped, ...rest } = body;
160
+ void _dropped;
161
+ const byKind = groupLiveRefsByKind(rawList);
162
+ return withResultBody(result, {
163
+ ...rest,
164
+ totalCount: rawList.length,
165
+ byKind,
166
+ referencedBy: [],
167
+ });
168
+ }
169
+ // balanced / full: keep the path list (paged when requested).
170
+ if (pageSize > 0) {
171
+ const { page, block } = applyPaging(rawList, "find_references", { page_size: pageSize, cursor });
172
+ const withPage = attachPagination({ ...body, referencedBy: page, totalCount: rawList.length }, block);
173
+ return withResultBody(result, withPage);
174
+ }
175
+ return withResultBody(result, { ...body, totalCount: rawList.length });
176
+ }
177
+ /** Derive a byKind grouping from flat referenced paths for the live compact view. */
178
+ function groupLiveRefsByKind(paths) {
179
+ const counts = {};
180
+ for (const path of paths) {
181
+ const dot = path.lastIndexOf(".");
182
+ const kind = dot > 0 ? path.slice(dot + 1).toLowerCase() : "other";
183
+ counts[kind] = (counts[kind] ?? 0) + 1;
184
+ }
185
+ return counts;
186
+ }
80
187
  // Operator-facing "what to do next" hint for each coarse status. Kept short
81
188
  // and action-oriented so the Validation Suite can render it inline.
82
189
  function bridgeStatusNextStep(status) {
@@ -87,10 +194,41 @@ function bridgeStatusNextStep(status) {
87
194
  return "Unity is compiling. Wait for the bridge to return to idle, or poll unity_open_mcp_bridge_status again.";
88
195
  case "stopped":
89
196
  return "Bridge listener is not reachable. Open the bridge window (Unity menu: Tools/Unity Open MCP Bridge) and ensure it is started, or launch Unity if it is not running, then call unity_open_mcp_bridge_status again to confirm.";
197
+ case "unreachable":
198
+ // M20 Plan 4-5 / T-fix-3 — distinct from "stopped": the Unity process is
199
+ // alive (lock PID live) but the listener did not respond. Usually a
200
+ // transient domain-reload window (the bridge tears down its HTTP socket
201
+ // for the reload duration) — retry shortly rather than treating it as a
202
+ // clean stop. This makes the reload-window flakiness visible instead of
203
+ // being masked by a clean-looking "stopped".
204
+ return "Bridge listener is not responding but Unity is running — likely a transient domain-reload window (the bridge tears down its HTTP socket during compiles). Wait a moment and call unity_open_mcp_bridge_status again; if it persists, call unity_open_mcp_read_compile_errors to check for a failed recompile.";
90
205
  case "dead_bridge":
91
- return "The bridge assembly failed to recompile and Unity is in a bad state. Call unity_open_mcp_read_compile_errors to retrieve the compiler errors from Editor.log, fix the cited file/line, then trigger a recompile.";
206
+ // M23 Plan 2 safe-mode surfacing (feedback 2026-06-29). A dead_bridge
207
+ // signature (live PID + stale heartbeat) almost always means Unity is
208
+ // sitting in Safe Mode / showing compile errors; the generic "open the
209
+ // bridge window" hint was misleading. Name safe mode explicitly and
210
+ // point at the one channel that works in that state.
211
+ return "Unity is likely in Safe Mode or showing compile errors — the bridge " +
212
+ "assembly failed to recompile, so the HTTP listener will not return on its " +
213
+ "own. Call unity_open_mcp_read_compile_errors to retrieve the compiler " +
214
+ "errors from Editor.log, fix the cited file/line, then trigger a recompile " +
215
+ "(e.g. a no-op edit + focus Unity, or unity_open_mcp_compile_check once the " +
216
+ "source compiles). If Unity is NOT in safe mode, the bridge toolbar toggle " +
217
+ "may be off — open the bridge window (Unity menu: Tools/Unity Open MCP " +
218
+ "Bridge) and confirm it is started.";
92
219
  }
93
220
  }
221
+ function bridgeStatusRecoveryHint(status) {
222
+ if (status === "dead_bridge") {
223
+ return {
224
+ tool: "unity_open_mcp_read_compile_errors",
225
+ reason: "Unity is likely in Safe Mode / compile failure — read_compile_errors " +
226
+ "reads Editor.log offline (the only channel that works with the bridge " +
227
+ "assembly dead).",
228
+ };
229
+ }
230
+ return null;
231
+ }
94
232
  export class ToolRouter {
95
233
  live;
96
234
  batch;
@@ -98,16 +236,36 @@ export class ToolRouter {
98
236
  eventStream;
99
237
  sessionState;
100
238
  onToolListChanged;
239
+ hubBackend;
101
240
  modelCache = new AssetModelCache();
102
- constructor(live, batch, projectPath, eventStream, sessionState, onToolListChanged) {
241
+ constructor(live, batch, projectPath, eventStream, sessionState, onToolListChanged,
242
+ // M26 Plan 2 — injectable Hub-control backend. Omit in production (the
243
+ // default calls the real hub-control functions); tests pass a fake to
244
+ // avoid real subprocess + network side effects.
245
+ hubBackend = defaultHubBackend) {
103
246
  this.live = live;
104
247
  this.batch = batch;
105
248
  this.projectPath = projectPath;
106
249
  this.eventStream = eventStream;
107
250
  this.sessionState = sessionState;
108
251
  this.onToolListChanged = onToolListChanged;
252
+ this.hubBackend = hubBackend;
109
253
  }
110
254
  async route(toolName, args) {
255
+ return this.routeCore(this.live, toolName, args);
256
+ }
257
+ /**
258
+ * M23 Plan 3 — route with a per-call live-client override. Used when a
259
+ * per-request `_meta.port` override targets a different bridge instance than
260
+ * the default. The override client bypasses shared session state (it is a
261
+ * fresh LiveClient aimed at the override port); all other routing logic is
262
+ * identical to {@link route}. When `overrideLive` is null the default client
263
+ * is used (identical to {@link route}).
264
+ */
265
+ async routeOverride(toolName, args, overrideLive) {
266
+ return this.routeCore(overrideLive, toolName, args);
267
+ }
268
+ async routeCore(live, toolName, args) {
111
269
  // list_assets — always offline (no live equivalent needed).
112
270
  if (toolName === "unity_open_mcp_list_assets") {
113
271
  return this.routeListAssets(args);
@@ -125,11 +283,11 @@ export class ToolRouter {
125
283
  // bridge is reachable; an unreachable bridge returns a clear error instead
126
284
  // of hanging on connect.
127
285
  if (toolName === "unity_senses_pull_events") {
128
- return this.routePullEvents(args);
286
+ return this.routePullEvents(args, live);
129
287
  }
130
288
  // capabilities — local capability-discovery surface (no live/batch hop).
131
289
  if (toolName === "unity_open_mcp_capabilities") {
132
- return this.routeCapabilities(args);
290
+ return this.routeCapabilities(args, live);
133
291
  }
134
292
  // list_rules — local rule catalog (no live/batch hop). Lets an agent
135
293
  // discover which rules apply to which asset kinds before calling
@@ -148,7 +306,7 @@ export class ToolRouter {
148
306
  // server applies the activate / deactivate filter to ListTools. Always
149
307
  // visible regardless of the current active set (meta-tool).
150
308
  if (toolName === "unity_open_mcp_manage_tools") {
151
- return this.routeManageTools(args);
309
+ return this.routeManageTools(args, live);
152
310
  }
153
311
  // testsuite-tauri phase-3 — bridge_status. Operator-only health snapshot.
154
312
  // Server-resolved (classifyInstance + one /ping via the LiveClient); never
@@ -156,18 +314,59 @@ export class ToolRouter {
156
314
  // `status` token (running/compiling/stopped/dead_bridge) the Validation
157
315
  // Suite app drives its offline-scenario gate off. Read-only, gate-free.
158
316
  if (toolName === "unity_open_mcp_bridge_status") {
159
- return this.routeBridgeStatus(args);
317
+ return this.routeBridgeStatus(args, live);
318
+ }
319
+ // M26 Plan 2 — Unity Hub control tools. Local-routed: resolved inside the
320
+ // MCP server (filesystem discovery + Unity archive feed + unityhub:// deep
321
+ // link + Hub CLI). Never hit the bridge tool endpoint, never spawn Unity.
322
+ // Mutating members are system-level ops (paths_hint N/A, gate-free).
323
+ if (toolName === "unity_open_mcp_hub_list_editors") {
324
+ return this.routeHubListEditors();
325
+ }
326
+ if (toolName === "unity_open_mcp_hub_available_releases") {
327
+ return this.routeHubAvailableReleases();
328
+ }
329
+ if (toolName === "unity_open_mcp_hub_install_editor") {
330
+ return this.routeHubInstallEditor(args);
331
+ }
332
+ if (toolName === "unity_open_mcp_hub_install_modules") {
333
+ return this.routeHubInstallModules(args);
334
+ }
335
+ if (toolName === "unity_open_mcp_hub_get_install_path") {
336
+ return this.routeHubGetInstallPath();
337
+ }
338
+ if (toolName === "unity_open_mcp_hub_set_install_path") {
339
+ return this.routeHubSetInstallPath(args);
160
340
  }
161
341
  // find_references — offline-first when no bridge is connected; live
162
342
  // ReferenceGraph remains available when the bridge is up.
163
343
  if (toolName === "unity_open_mcp_find_references") {
164
- return this.routeFindReferences(args);
344
+ return this.routeFindReferences(args, live);
345
+ }
346
+ // dependencies — offline-first (M24 Plan 2 / T24.2). Forward + reverse
347
+ // edges + impact analysis all compute from YAML on disk when no bridge is
348
+ // connected; the live DependenciesTool remains available when the bridge
349
+ // is up. The offline path is the only one that exposes include_impact
350
+ // (transitive reverse closure) today.
351
+ if (toolName === "unity_open_mcp_dependencies") {
352
+ return this.routeDependencies(args, live);
353
+ }
354
+ // M22 — verify tools. The bridge returns the full issues[] list; we fold +
355
+ // page server-side per the output profile (compact = counts only).
356
+ if (toolName === "unity_open_mcp_validate_edit" || toolName === "unity_open_mcp_scan_paths") {
357
+ return this.routeVerifyResult(toolName, args, live);
358
+ }
359
+ // M22 — scene_get_data. Resolves the output profile (onto detail) and pages
360
+ // the node stream server-side; the bridge detail/max_nodes params keep
361
+ // working as back-compat aliases.
362
+ if (toolName === "unity_open_mcp_scene_get_data") {
363
+ return this.routeSceneGetData(args, live);
165
364
  }
166
365
  // Compact drill-down reads: offline-first for text-serialized assets, fall
167
366
  // back to live bridge for binary formats. The compressible-router handles
168
367
  // the source selection internally.
169
368
  if (isCompressible(toolName)) {
170
- const result = await routeCompressible(toolName, args, this.live, this.modelCache, this.projectPath);
369
+ const result = await routeCompressible(toolName, args, live, this.modelCache, this.projectPath);
171
370
  return injectRouteMeta(result, { route: "live" });
172
371
  }
173
372
  const canBatch = this.batch.isBatchTool(toolName);
@@ -186,13 +385,13 @@ export class ToolRouter {
186
385
  });
187
386
  }
188
387
  if (!canBatch && !isPing) {
189
- const result = await this.live.route(toolName, args);
388
+ const result = await live.route(toolName, args);
190
389
  return injectRouteMeta(result, { route: "live" });
191
390
  }
192
- const liveAvailable = await this.live.isLiveAvailable();
391
+ const liveAvailable = await live.isLiveAvailable();
193
392
  if (liveAvailable) {
194
393
  console.error(`[unity-open-mcp] Route: ${toolName} -> live`);
195
- const result = await this.live.route(toolName, args);
394
+ const result = await live.route(toolName, args);
196
395
  return injectRouteMeta(result, { route: "live" });
197
396
  }
198
397
  console.error(`[unity-open-mcp] Route: ${toolName} -> batch (live bridge unavailable)`);
@@ -334,8 +533,8 @@ export class ToolRouter {
334
533
  // (a fresh subscriber id is minted per process), so a restarted MCP server
335
534
  // begins "now" — agents that care about historical logs should still call
336
535
  // unity_senses_read_console.
337
- async routePullEvents(args) {
338
- const liveAvailable = await this.live.isLiveAvailable();
536
+ async routePullEvents(args, live) {
537
+ const liveAvailable = await live.isLiveAvailable();
339
538
  if (!liveAvailable) {
340
539
  return {
341
540
  content: [
@@ -365,7 +564,7 @@ export class ToolRouter {
365
564
  isError: false,
366
565
  };
367
566
  }
368
- async routeCapabilities(args) {
567
+ async routeCapabilities(args, live) {
369
568
  const kind = args.kind === "tools" || args.kind === "rules" || args.kind === "fixes"
370
569
  ? args.kind
371
570
  : undefined;
@@ -376,12 +575,18 @@ export class ToolRouter {
376
575
  // Capabilities stays local-route; the bridge probe is a read-only fetch
377
576
  // that does not change the route classification.
378
577
  let availableBridgeTools;
379
- const liveAvailable = await this.live.isLiveAvailable();
578
+ const liveAvailable = await live.isLiveAvailable();
380
579
  if (liveAvailable) {
381
- const inventory = await this.live.listBridgeTools();
580
+ const inventory = await live.listBridgeTools();
382
581
  if (inventory)
383
582
  availableBridgeTools = inventory.tools;
384
583
  }
584
+ // M20 Plan 7 / T20.7.0 — reconcile auto-activation before reporting the
585
+ // group catalog so the capability output reflects packages that came
586
+ // online since the last call. Auto-activated groups surface with
587
+ // `autoActivated: true` + `packageDependency` (see build-capabilities).
588
+ // Reuse the inventory already fetched above (no second GET /tools hop).
589
+ await this.reconcileAutoActivation(availableBridgeTools ? { tools: availableBridgeTools } : null);
385
590
  const result = buildCapabilities({
386
591
  tools: ALL_TOOLS,
387
592
  batchToolNames: BATCH_TOOL_NAMES,
@@ -459,8 +664,8 @@ export class ToolRouter {
459
664
  // M18 Plan 2 / T18.2.2 — manage_tools routes local. Mutates the per-
460
665
  // session tool-group visibility state (ToolSessionState) that ListTools
461
666
  // consults to filter tools. The bridge does not see these calls — it is
462
- // session-state the MCP server owns (matches the Coplay model and the
463
- // resolved decision in M18 execution-plan.md).
667
+ // session-state the MCP server owns (the resolved decision in M18
668
+ // execution-plan.md).
464
669
  //
465
670
  // Compiled-state availability (whether the bridge compiled the domain in)
466
671
  // is reported in the `available` + `availableReason` fields per group. The
@@ -468,11 +673,11 @@ export class ToolRouter {
468
673
  // when reachable; when the bridge is offline it reports `available: null`
469
674
  // (unknown) and the agent should fall back to `unity_open_mcp_capabilities`
470
675
  // for the authoritative compiled-state report.
471
- async routeManageTools(args) {
676
+ async routeManageTools(args, live) {
472
677
  const action = typeof args.action === "string" ? args.action : "";
473
678
  const group = typeof args.group === "string" ? args.group.trim() : "";
474
679
  if (action === "list_groups") {
475
- return this.manageToolsListGroups();
680
+ return this.manageToolsListGroups(live);
476
681
  }
477
682
  if (action === "reset") {
478
683
  const before = this.sessionState.activeGroups();
@@ -516,9 +721,20 @@ export class ToolRouter {
516
721
  }
517
722
  return this.manageToolsError("unknown_action", `Unknown action '${action}'. Valid actions: list_groups, activate, deactivate, reset.`);
518
723
  }
519
- async manageToolsListGroups() {
724
+ async manageToolsListGroups(live) {
725
+ // M20 Plan 7 / T20.7.0 — fetch the compiled-state inventory once and
726
+ // reuse it for both auto-activation reconciliation and compiled-state
727
+ // availability (two GET /tools hops → one). When the bridge is offline
728
+ // both paths degrade gracefully (no auto-activation; availability=null).
729
+ const liveAvailable = await live.isLiveAvailable();
730
+ const inventory = liveAvailable
731
+ ? await live.listBridgeTools()
732
+ : null;
733
+ // Reconcile auto-activation first so the listed active set reflects
734
+ // packages that came online since the last call.
735
+ await this.reconcileAutoActivation(inventory ? { tools: inventory.tools } : null);
520
736
  const groupsList = groupToTools();
521
- const compiledAvailability = await this.resolveCompiledAvailability();
737
+ const compiledAvailability = this.computeCompiledAvailability(inventory, groupsList);
522
738
  const groups = TOOL_GROUPS.map((g) => {
523
739
  const tools = groupsList[g.id] ?? [];
524
740
  const availability = compiledAvailability.get(g.id);
@@ -526,6 +742,12 @@ export class ToolRouter {
526
742
  id: g.id,
527
743
  description: g.description,
528
744
  active: this.sessionState.isGroupActive(g.id),
745
+ // M20 Plan 7 / T20.7.0 — why the group is active (default / manual /
746
+ // auto). `null` when the group is not active. Lets an agent learn
747
+ // that a group is visible because its package is installed, not
748
+ // because the operator opted in.
749
+ activationSource: this.sessionState.activationSource(g.id),
750
+ autoActivated: this.sessionState.activationSource(g.id) === "auto",
529
751
  defaultEnabled: g.defaultEnabled,
530
752
  // Compiled-state availability of the domain dependency. `true` for
531
753
  // groups without a domainDefine (always compiled in); `false` /
@@ -536,6 +758,7 @@ export class ToolRouter {
536
758
  available: availability?.available ?? null,
537
759
  availableReason: availability?.reason ?? null,
538
760
  unityPackage: g.unityPackage ?? null,
761
+ packageDependency: g.unityPackage ?? null,
539
762
  toolCount: tools.length,
540
763
  tools,
541
764
  };
@@ -573,16 +796,69 @@ export class ToolRouter {
573
796
  return;
574
797
  await this.onToolListChanged();
575
798
  }
799
+ // M20 Plan 7 / T20.7.0 — reconcile the auto-activated set against the
800
+ // currently-compiled-in bridge inventory. A group with `autoActivate: true`
801
+ // is considered package-present when any of its compiled-in tool names
802
+ // appears in the bridge's `GET /tools` inventory — the same signal
803
+ // `resolveCompiledAvailability` uses, so no extra package_list round-trip.
804
+ // The session store records the outcome; we fire the listChanged
805
+ // notification exactly once when the active set changed. Returns true when
806
+ // the active set changed (so callers like capabilities / list_groups can
807
+ // report a fresh snapshot).
808
+ //
809
+ // Callers that have ALREADY fetched the bridge inventory (capabilities,
810
+ // list_groups) pass it via `prefetchedInventory` to avoid a second
811
+ // `GET /tools` round-trip on the same call.
812
+ async reconcileAutoActivation(prefetchedInventory) {
813
+ if (AUTO_ACTIVATE_GROUPS.length === 0)
814
+ return false;
815
+ let inventory = prefetchedInventory ?? null;
816
+ if (!inventory) {
817
+ const liveAvailable = await this.live.isLiveAvailable();
818
+ if (!liveAvailable)
819
+ return false;
820
+ inventory = await this.live.listBridgeTools();
821
+ if (!inventory)
822
+ return false;
823
+ }
824
+ const groupsList = groupToTools();
825
+ const satisfied = new Set();
826
+ for (const entry of AUTO_ACTIVATE_GROUPS) {
827
+ const groupTools = groupsList[entry.groupId] ?? [];
828
+ const anyCompiledIn = groupTools.length > 0 &&
829
+ groupTools.some((t) => inventory.tools.has(t));
830
+ if (anyCompiledIn)
831
+ satisfied.add(entry.groupId);
832
+ }
833
+ const before = this.sessionState.activeGroups();
834
+ const changed = this.sessionState.reconcileAutoActivation(satisfied);
835
+ if (changed.length > 0) {
836
+ await this.maybeNotifyToolListChanged(before);
837
+ return true;
838
+ }
839
+ return false;
840
+ }
576
841
  // Resolve compiled-state availability per group from the live bridge. The
577
842
  // bridge reports which tools it compiled in via `GET /tools`; a domain-
578
843
  // gated group is `available: true` when any of its compiled-in tool names
579
844
  // appears in that set, `false` when none do (Unity domain package not
580
845
  // installed), and `null` (unknown) when the bridge is offline.
846
+ //
847
+ // Thin async wrapper over {@link computeCompiledAvailability}: fetches the
848
+ // inventory (or null when the bridge is offline) and delegates. Kept for
849
+ // the few call sites that don't already have an inventory in hand.
581
850
  async resolveCompiledAvailability() {
582
- const out = new Map();
583
851
  const liveAvailable = await this.live.isLiveAvailable();
584
- const inventory = liveAvailable ? await this.live.listBridgeTools() : null;
585
- const groupsList = groupToTools();
852
+ const inventory = liveAvailable
853
+ ? await this.live.listBridgeTools()
854
+ : null;
855
+ return this.computeCompiledAvailability(inventory, groupToTools());
856
+ }
857
+ // Pure per-group availability computation given a (possibly null) bridge
858
+ // tool inventory. Extracted so callers that already fetched the inventory
859
+ // (capabilities, list_groups) can avoid a second GET /tools hop.
860
+ computeCompiledAvailability(inventory, groupsList) {
861
+ const out = new Map();
586
862
  for (const g of TOOL_GROUPS) {
587
863
  if (!g.domainDefine) {
588
864
  out.set(g.id, { available: true, reason: null });
@@ -640,6 +916,7 @@ export class ToolRouter {
640
916
  // lock="dead_bridge" → "dead_bridge"
641
917
  // /ping reachable, compiling=true → "compiling"
642
918
  // /ping reachable, connected=true → "running"
919
+ // /ping UNreachable + Unity running → "unreachable" (T-fix-3)
643
920
  // otherwise (offline / toolbar off / gone) → "stopped"
644
921
  //
645
922
  // "stopped" intentionally folds two indistinguishable cases from the
@@ -648,7 +925,15 @@ export class ToolRouter {
648
925
  // lock summary in the response disambiguates them (`lock === null` → no
649
926
  // Unity; `lock.pid` alive but no listener → toolbar off). The tool never
650
927
  // errors on an offline bridge — `stopped` IS the answer in that case.
651
- async routeBridgeStatus(_args) {
928
+ //
929
+ // M20 Plan 4-5 / T-fix-3 — "unreachable" splits out the case where Unity IS
930
+ // running (lock PID alive, often mid-reload) but the listener did not
931
+ // respond. Pre-fix this collapsed to "stopped", hiding transient reload-
932
+ // window flakiness behind a clean-looking status. "unreachable" makes it
933
+ // visible so agents retry instead of treating it as a clean stop. The
934
+ // `isError:false` contract is preserved — this is richer status, not an
935
+ // error path.
936
+ async routeBridgeStatus(_args, live) {
652
937
  const lockOnDisk = lockPath(this.projectPath);
653
938
  let lock = null;
654
939
  try {
@@ -664,11 +949,17 @@ export class ToolRouter {
664
949
  // — both shapes are valid inputs here. A compile-in-progress bridge
665
950
  // returns HTTP 503 from /ping which isLiveAvailable() treats as reachable;
666
951
  // the ping body's `compiling` field is the authoritative signal.
667
- const pingResult = await this.live.route("unity_open_mcp_ping", {});
952
+ const pingResult = await live.route("unity_open_mcp_ping", {});
668
953
  const pingBody = parsePingBody(pingResult);
669
954
  const pingReachable = !pingResult.isError && pingBody !== null;
670
955
  const compiling = pingReachable === true && pingBody?.compiling === true;
671
956
  const connected = pingReachable === true && pingBody?.connected === true;
957
+ // T-fix-3 — when the ping is unreachable, distinguish "Unity is running but
958
+ // the listener is momentarily down" (lock PID alive / reload window) from a
959
+ // genuine "nothing there" stop. classification "reloading" covers the
960
+ // domain-reload window; a live-PID "healthy"/"gone"-with-alive-pid lock
961
+ // also indicates Unity is up.
962
+ const lockPidAlive = lock !== null && isPidAlive(lock.pid);
672
963
  let status;
673
964
  if (classification === "dead_bridge") {
674
965
  status = "dead_bridge";
@@ -679,6 +970,12 @@ export class ToolRouter {
679
970
  else if (connected) {
680
971
  status = "running";
681
972
  }
973
+ else if (!pingReachable && (classification === "reloading" || lockPidAlive)) {
974
+ // Unity is running but the listener didn't respond — transient (reload
975
+ // window) or toolbar-off. Surface as unreachable so it isn't masked by
976
+ // a clean "stopped".
977
+ status = "unreachable";
978
+ }
682
979
  else {
683
980
  status = "stopped";
684
981
  }
@@ -689,6 +986,17 @@ export class ToolRouter {
689
986
  // uses for poll termination).
690
987
  ready: status === "running",
691
988
  projectPath: this.projectPath,
989
+ // M23 Plan 2 — top-level mirror of the instance-lock classification
990
+ // (healthy | reloading | dead_bridge | gone) + a structured recovery
991
+ // hint. Mirrors `instance.classification` at the top level so agents
992
+ // can branch on a single field without digging into the instance
993
+ // sub-object; `recoveryHint` is null unless the status has a specific
994
+ // recovery tool to call (today: dead_bridge → read_compile_errors).
995
+ // Feedback 2026-06-29: a dead_bridge must read as "Unity likely in
996
+ // Safe Mode / compile failure", not the generic "stopped" the bare
997
+ // status used to suggest.
998
+ classification,
999
+ recoveryHint: bridgeStatusRecoveryHint(status),
692
1000
  instance: {
693
1001
  lockPath: lockOnDisk,
694
1002
  classification,
@@ -716,12 +1024,146 @@ export class ToolRouter {
716
1024
  isError: false,
717
1025
  };
718
1026
  }
719
- async routeFindReferences(args) {
720
- const liveAvailable = await this.live.isLiveAvailable();
1027
+ // ── M26 Plan 2 — Unity Hub control (local-routed) ────────────────
1028
+ //
1029
+ // Each of these resolves entirely inside the MCP server (no bridge tool
1030
+ // endpoint, no batch Unity) → every response is tagged `_source: "local"`.
1031
+ // Read-only members are plain JSON bodies; mutating members return the
1032
+ // gate-consistent mutation/gate/agentNextSteps envelope shape so agents
1033
+ // parse them uniformly, with gate.skipped=true because these are system-
1034
+ // level ops where the project-asset gate does not apply (paths_hint N/A).
1035
+ async routeHubListEditors() {
1036
+ const editors = this.hubBackend.listInstalledEditors();
1037
+ const body = {
1038
+ editors,
1039
+ count: editors.length,
1040
+ _source: "local",
1041
+ };
1042
+ return {
1043
+ content: [{ type: "text", text: JSON.stringify(body) }],
1044
+ isError: false,
1045
+ };
1046
+ }
1047
+ async routeHubAvailableReleases() {
1048
+ const result = await this.hubBackend.fetchAvailableReleases();
1049
+ const body = {
1050
+ entries: result.entries,
1051
+ count: result.entries.length,
1052
+ stale: result.stale,
1053
+ fetchedAt: result.fetchedAt,
1054
+ _source: "local",
1055
+ };
1056
+ return {
1057
+ content: [{ type: "text", text: JSON.stringify(body) }],
1058
+ isError: false,
1059
+ };
1060
+ }
1061
+ async routeHubInstallEditor(args) {
1062
+ const version = typeof args.version === "string" ? args.version : "";
1063
+ const changeset = typeof args.changeset === "string" ? args.changeset : undefined;
1064
+ if (!version.trim()) {
1065
+ return localError("missing_parameter", "version is required.");
1066
+ }
1067
+ const res = this.hubBackend.openInstallDeepLink(version, changeset);
1068
+ return hubMutationEnvelope({
1069
+ success: res.opened,
1070
+ output: { deepLink: res.deepLink, version, changeset: changeset ?? null },
1071
+ error: res.error,
1072
+ nextSteps: res.opened
1073
+ ? [
1074
+ "Unity Hub opened at the install dialog for " +
1075
+ version +
1076
+ ". The download runs inside the Hub (watch its progress UI).",
1077
+ "There is no in-call completion detection — poll " +
1078
+ "unity_open_mcp_hub_list_editors after the Hub finishes to confirm the new editor.",
1079
+ ]
1080
+ : [
1081
+ "Ensure Unity Hub is installed and registered as the unityhub:// handler, then retry.",
1082
+ "Fallback: open the release-notes URL from hub_available_releases in a browser and install manually.",
1083
+ ],
1084
+ isError: !res.opened,
1085
+ });
1086
+ }
1087
+ async routeHubInstallModules(args) {
1088
+ const version = typeof args.version === "string" ? args.version : "";
1089
+ const changeset = typeof args.changeset === "string" ? args.changeset : undefined;
1090
+ const modules = Array.isArray(args.modules)
1091
+ ? args.modules.filter((m) => typeof m === "string")
1092
+ : [];
1093
+ if (!version.trim()) {
1094
+ return localError("missing_parameter", "version is required.");
1095
+ }
1096
+ const res = this.hubBackend.openInstallDeepLink(version, changeset);
1097
+ return hubMutationEnvelope({
1098
+ success: res.opened,
1099
+ output: {
1100
+ deepLink: res.deepLink,
1101
+ version,
1102
+ changeset: changeset ?? null,
1103
+ modulesRequested: modules,
1104
+ note: "The unityhub:// scheme does not expose a module-specific deep link; the Hub opens at the version's install dialog where the operator selects the modules.",
1105
+ },
1106
+ error: res.error,
1107
+ nextSteps: res.opened
1108
+ ? [
1109
+ `Unity Hub opened at the install dialog for ${version}. Select the requested modules (${modules.join(", ") || "none specified"}) in the Hub UI.`,
1110
+ "Poll unity_open_mcp_hub_list_editors (which scans Data/PlaybackEngines) afterwards to confirm the new modules.",
1111
+ ]
1112
+ : [
1113
+ "Ensure Unity Hub is installed and registered as the unityhub:// handler, then retry.",
1114
+ ],
1115
+ isError: !res.opened,
1116
+ });
1117
+ }
1118
+ async routeHubGetInstallPath() {
1119
+ const res = this.hubBackend.getInstallPath();
1120
+ const body = {
1121
+ path: res.path,
1122
+ source: res.source,
1123
+ error: res.error,
1124
+ _source: "local",
1125
+ };
1126
+ return {
1127
+ content: [{ type: "text", text: JSON.stringify(body) }],
1128
+ isError: res.error !== null,
1129
+ };
1130
+ }
1131
+ async routeHubSetInstallPath(args) {
1132
+ const path = typeof args.path === "string" ? args.path : "";
1133
+ if (!path.trim()) {
1134
+ return localError("missing_parameter", "path is required.");
1135
+ }
1136
+ const res = this.hubBackend.setInstallPath(path);
1137
+ return hubMutationEnvelope({
1138
+ success: res.success,
1139
+ output: { path, hubCliOutput: res.output },
1140
+ error: res.error,
1141
+ nextSteps: res.success
1142
+ ? [`Default Unity Editor install path set to ${path}.`]
1143
+ : [
1144
+ "Set the UNITY_HUB_PATH env var to the Unity Hub binary if the CLI was not found, then retry.",
1145
+ ],
1146
+ isError: !res.success,
1147
+ });
1148
+ }
1149
+ async routeFindReferences(args, live) {
1150
+ // M22 — resolve profile -> detail. compact (default) maps to summary
1151
+ // (counts/byKind/byFolder only); balanced -> normal; full -> verbose.
1152
+ const { detail, profile } = readProfileAndDetail(args, "summary");
1153
+ const wantPaging = typeof args.page_size === "number" && args.page_size > 0;
1154
+ const liveAvailable = await live.isLiveAvailable();
721
1155
  if (liveAvailable) {
722
1156
  console.error("[unity-open-mcp] Route: find_references -> live");
723
- const result = await this.live.route("unity_open_mcp_find_references", args);
724
- return injectRouteMeta(result, { route: "live" });
1157
+ // The live bridge only honors max_results (no detail/per-file/threshold).
1158
+ // Ask for the full set when paging so the cursor can walk it; otherwise
1159
+ // honor the legacy max_results cap.
1160
+ const liveArgs = {
1161
+ ...args,
1162
+ max_results: wantPaging ? 0 : (typeof args.max_results === "number" ? args.max_results : 100),
1163
+ };
1164
+ const result = await live.route("unity_open_mcp_find_references", liveArgs);
1165
+ const folded = foldFindReferencesLive(result, profile, wantPaging, args);
1166
+ return injectRouteMeta(folded, { route: "live" });
725
1167
  }
726
1168
  console.error("[unity-open-mcp] Route: find_references -> offline (live bridge unavailable)");
727
1169
  const assetPath = typeof args.asset_path === "string" ? args.asset_path : undefined;
@@ -746,16 +1188,98 @@ export class ToolRouter {
746
1188
  const result = await findReferencesOffline({
747
1189
  assetPath,
748
1190
  guid,
749
- detail: typeof args.detail === "string" ? args.detail : "normal",
750
- maxResults: typeof args.max_results === "number" ? args.max_results : 100,
1191
+ detail,
1192
+ // When paging, fetch the full set; otherwise honor the legacy cap.
1193
+ maxResults: wantPaging ? 0 : (typeof args.max_results === "number" ? args.max_results : 100),
751
1194
  maxPerFile: typeof args.max_per_file === "number" ? args.max_per_file : 5,
752
1195
  patternThreshold: typeof args.pattern_threshold === "number" ? args.pattern_threshold : 0,
753
1196
  projectRoot: this.projectPath,
754
1197
  });
1198
+ // M22 — page the referencedBy list when requested (balanced/full only;
1199
+ // compact returns an empty list by design).
1200
+ const withPaging = wantPaging
1201
+ ? pageFindReferencesResult(result, args.page_size, typeof args.cursor === "string" ? args.cursor : undefined)
1202
+ : result;
1203
+ return {
1204
+ content: [
1205
+ { type: "text", text: JSON.stringify({ ...withPaging, _source: "offline" }) },
1206
+ ],
1207
+ isError: false,
1208
+ };
1209
+ }
1210
+ catch (err) {
1211
+ const message = err instanceof Error ? err.message : String(err);
1212
+ return {
1213
+ content: [
1214
+ {
1215
+ type: "text",
1216
+ text: JSON.stringify({
1217
+ error: { code: "offline_error", message },
1218
+ }),
1219
+ },
1220
+ ],
1221
+ isError: true,
1222
+ };
1223
+ }
1224
+ }
1225
+ // M24 Plan 2 / T24.2 — dependencies (forward + reverse edges + impact
1226
+ // analysis). Offline-first when no bridge is connected; the live
1227
+ // DependenciesTool is the fallback when the bridge is up. The live tool does
1228
+ // not yet honor include_impact / max_impact_depth — those are offline-only
1229
+ // today, so when the bridge is up and impact is requested we route offline
1230
+ // regardless (the offline path is the only one with the transitive closure).
1231
+ async routeDependencies(args, live) {
1232
+ const { detail } = readProfileAndDetail(args, "normal");
1233
+ const assetPath = typeof args.asset_path === "string" ? args.asset_path : undefined;
1234
+ const guid = typeof args.guid === "string" ? args.guid : undefined;
1235
+ const includeImpact = args.include_impact === true;
1236
+ if (!assetPath && !guid) {
755
1237
  return {
756
1238
  content: [
757
- { type: "text", text: JSON.stringify({ ...result, _source: "offline" }) },
1239
+ {
1240
+ type: "text",
1241
+ text: JSON.stringify({
1242
+ error: {
1243
+ code: "missing_parameter",
1244
+ message: "Either 'asset_path' or 'guid' is required.",
1245
+ },
1246
+ }),
1247
+ },
758
1248
  ],
1249
+ isError: true,
1250
+ };
1251
+ }
1252
+ const liveAvailable = await live.isLiveAvailable();
1253
+ // Impact analysis is offline-only. When requested, go straight to the
1254
+ // offline path even if the bridge is up — the live DependenciesTool has no
1255
+ // transitive-closure surface yet.
1256
+ if (!includeImpact && liveAvailable) {
1257
+ console.error("[unity-open-mcp] Route: dependencies -> live");
1258
+ // The live tool honors asset_path/guid/detail/max_results only.
1259
+ const liveArgs = {
1260
+ asset_path: assetPath,
1261
+ guid,
1262
+ detail,
1263
+ max_results: typeof args.max_results === "number" ? args.max_results : 100,
1264
+ };
1265
+ const result = await live.route("unity_open_mcp_dependencies", liveArgs);
1266
+ return injectRouteMeta(result, { route: "live" });
1267
+ }
1268
+ console.error(includeImpact && liveAvailable
1269
+ ? "[unity-open-mcp] Route: dependencies -> offline (include_impact is offline-only)"
1270
+ : "[unity-open-mcp] Route: dependencies -> offline (live bridge unavailable)");
1271
+ try {
1272
+ const result = await dependenciesOffline({
1273
+ assetPath,
1274
+ guid,
1275
+ detail,
1276
+ maxResults: typeof args.max_results === "number" ? args.max_results : 100,
1277
+ includeImpact,
1278
+ maxImpactDepth: typeof args.max_impact_depth === "number" ? args.max_impact_depth : 5,
1279
+ projectRoot: this.projectPath,
1280
+ });
1281
+ return {
1282
+ content: [{ type: "text", text: JSON.stringify(result) }],
759
1283
  isError: false,
760
1284
  };
761
1285
  }
@@ -774,5 +1298,134 @@ export class ToolRouter {
774
1298
  };
775
1299
  }
776
1300
  }
1301
+ // M22 — verify tools (validate_edit / scan_paths). The bridge always returns
1302
+ // the full issues[] list; we fold + page server-side per the output profile
1303
+ // (compact = counts only, balanced/full keep + page issues).
1304
+ async routeVerifyResult(toolName, args, live) {
1305
+ const { profile } = readProfileAndDetail(args, "summary");
1306
+ const pageSize = typeof args.page_size === "number" && args.page_size > 0 ? args.page_size : 0;
1307
+ const cursor = typeof args.cursor === "string" ? args.cursor : undefined;
1308
+ // Forward to live/batch exactly as the generic path would. The bridge
1309
+ // ignores the unknown profile/page_size/cursor keys (JsonBody substring-
1310
+ // searches only for keys it asks for), so they pass through harmlessly.
1311
+ const canBatch = this.batch.isBatchTool(toolName);
1312
+ let raw;
1313
+ if (!canBatch) {
1314
+ raw = await live.route(toolName, args);
1315
+ raw = injectRouteMeta(raw, { route: "live" });
1316
+ }
1317
+ else {
1318
+ const liveAvailable = await live.isLiveAvailable();
1319
+ if (liveAvailable) {
1320
+ raw = await live.route(toolName, args);
1321
+ raw = injectRouteMeta(raw, { route: "live" });
1322
+ }
1323
+ else {
1324
+ raw = await this.batch.route(toolName, args);
1325
+ raw = injectRouteMeta(raw, { route: "batch", fallbackReason: "live_unavailable" });
1326
+ }
1327
+ }
1328
+ const body = parseResultBody(raw);
1329
+ if (body === null)
1330
+ return raw;
1331
+ if (body.error)
1332
+ return raw;
1333
+ const folded = foldVerifyResult(body, toolName, profile, { page_size: pageSize, cursor });
1334
+ return withResultBody(raw, folded);
1335
+ }
1336
+ // M22 — scene_get_data. Resolves the output profile (onto detail) and pages
1337
+ // the node stream server-side. The bridge detail/max_nodes params keep
1338
+ // working as back-compat aliases; profile wins when both are present.
1339
+ async routeSceneGetData(args, live) {
1340
+ const { detail } = readProfileAndDetail(args, "summary");
1341
+ const wantPaging = typeof args.page_size === "number" && args.page_size > 0;
1342
+ // Build the args the bridge sees: profile resolved to detail (the bridge's
1343
+ // real knob), legacy max_nodes honored, paging keys stripped (the bridge
1344
+ // would ignore them anyway, but keep the call clean).
1345
+ const bridgeArgs = { ...args };
1346
+ delete bridgeArgs.profile;
1347
+ delete bridgeArgs.page_size;
1348
+ delete bridgeArgs.cursor;
1349
+ bridgeArgs.detail = detail;
1350
+ const canBatch = this.batch.isBatchTool("unity_open_mcp_scene_get_data");
1351
+ let raw;
1352
+ if (!canBatch) {
1353
+ raw = await live.route("unity_open_mcp_scene_get_data", bridgeArgs);
1354
+ raw = injectRouteMeta(raw, { route: "live" });
1355
+ }
1356
+ else {
1357
+ const liveAvailable = await live.isLiveAvailable();
1358
+ if (liveAvailable) {
1359
+ raw = await live.route("unity_open_mcp_scene_get_data", bridgeArgs);
1360
+ raw = injectRouteMeta(raw, { route: "live" });
1361
+ }
1362
+ else {
1363
+ raw = await this.batch.route("unity_open_mcp_scene_get_data", bridgeArgs);
1364
+ raw = injectRouteMeta(raw, { route: "batch", fallbackReason: "live_unavailable" });
1365
+ }
1366
+ }
1367
+ if (!wantPaging)
1368
+ return raw;
1369
+ const body = parseResultBody(raw);
1370
+ if (body === null)
1371
+ return raw;
1372
+ if (body.error)
1373
+ return raw;
1374
+ const pageSize = args.page_size;
1375
+ const cursor = typeof args.cursor === "string" ? args.cursor : undefined;
1376
+ const paged = pageSceneNodes(body, pageSize, cursor);
1377
+ return withResultBody(raw, paged);
1378
+ }
1379
+ }
1380
+ /**
1381
+ * Page the scene_get_data node stream. The bridge emits a per-root `roots`
1382
+ * array (summary: root roster) or a nested hierarchy (normal/verbose). We page
1383
+ * the FLATTENED node stream so the cursor is a stable position regardless of
1384
+ * the detail mode, then re-hang the page as a flat `nodes` array with the
1385
+ * original scene metadata preserved.
1386
+ */
1387
+ function pageSceneNodes(body, pageSize, cursor) {
1388
+ const flat = flattenSceneBody(body);
1389
+ const { page, block } = applyPaging(flat, "scene_get_data", { page_size: pageSize, cursor });
1390
+ // Preserve scene-level metadata; replace the structural root/hierarchy fields
1391
+ // with the flat paged node list + a note explaining the page shape.
1392
+ const { roots, rootGameObjects, nodes, truncated, pagination: _existing, ...meta } = body;
1393
+ void roots;
1394
+ void rootGameObjects;
1395
+ void nodes;
1396
+ void truncated;
1397
+ void _existing;
1398
+ return attachPagination({
1399
+ ...meta,
1400
+ nodes: page,
1401
+ pagingNote: "page_size returns a flattened node stream; re-read without page_size for the structural root/hierarchy view",
1402
+ }, block);
1403
+ }
1404
+ /**
1405
+ * Flatten the scene body into a node stream for paging. Handles both the
1406
+ * summary root roster (array of root objects) and the nested normal/verbose
1407
+ * hierarchy (objects with `children`).
1408
+ */
1409
+ function flattenSceneBody(body) {
1410
+ const out = [];
1411
+ const walk = (node) => {
1412
+ if (!node || typeof node !== "object")
1413
+ return;
1414
+ const obj = node;
1415
+ out.push(obj);
1416
+ const children = obj.children;
1417
+ if (Array.isArray(children)) {
1418
+ for (const child of children)
1419
+ walk(child);
1420
+ }
1421
+ };
1422
+ // The bridge emits `roots` (summary) or `rootGameObjects` (normal/verbose);
1423
+ // accept either.
1424
+ const rootField = Array.isArray(body.roots) ? body.roots : body.rootGameObjects;
1425
+ if (Array.isArray(rootField)) {
1426
+ for (const root of rootField)
1427
+ walk(root);
1428
+ }
1429
+ return out;
777
1430
  }
778
1431
  //# sourceMappingURL=tool-router.js.map