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,14 +1,37 @@
1
1
  import { deriveIsError } from "./gate-error.js";
2
- import { existsSync, readFileSync, unlinkSync } from "node:fs";
2
+ import { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { homedir } from "node:os";
5
- import { pollAndDismissLaunchErrors, readDismissConfig, } from "./dialog-dismiss.js";
6
- import { readInstanceLock, classifyInstance, lockPath } from "./instance-discovery.js";
5
+ import { pollAndDismissDialogs, readDismissConfig, } from "./dialog-dismiss.js";
6
+ import { readInstanceLock, classifyInstance, lockPath, isPidAlive } from "./instance-discovery.js";
7
7
  import { makeErrorResult } from "./results.js";
8
8
  import { checkBridgeCompat, isVersionCheckSuppressed, } from "./compat.js";
9
- const MAX_COMPILE_WAIT_MS = 120_000;
10
- const COMPILE_POLL_INTERVAL_MS = 2_000;
9
+ // M23 Plan 3 — structured retry policy + env-overridable tunables. Replaces
10
+ // the hardcoded module constants that previously bounded the compile-wait and
11
+ // transient-retry loops. The policy is keyed by lifecycle class (see
12
+ // retryConfigFor); the tunables are resolved once at construction.
13
+ import { readRetryTunables, } from "./retry-policy.js";
14
+ // M23 Plan 3 — compile-verify failure-code detection (compile_noop /
15
+ // dll_stale). Applied as an additive annotation on compile-reload results so
16
+ // an agent can branch when a recompile reported success but the compiled state
17
+ // did not advance.
18
+ import { detectCompileVerify, buildCompileVerifyAnnotation, } from "./compile-verify.js";
19
+ import { lifecycleFor } from "./capabilities/lifecycle.js";
20
+ // M23 Plan 3 — per-process agent identity (sent as X-Agent-Id so the bridge's
21
+ // fair round-robin queue can schedule across agents).
22
+ import { PROCESS_AGENT_ID } from "./agent-identity.js";
11
23
  const PING_TIMEOUT_MS = 5_000;
24
+ // The bridge's default per-tool wait before IT gives up and returns a timeout
25
+ // envelope (packages/bridge/Editor/Bridge/BridgeRequestBody.cs
26
+ // DefaultTimeoutMs). The client fetch timeout must never preempt this — if the
27
+ // client aborts first it re-POSTs while the bridge is still processing the
28
+ // original Work, manufacturing duplicate side-effects (specs/feedback.md entry
29
+ // 4B/4A). postTool floors its fetch timeout at BRIDGE_DEFAULT_TIMEOUT_MS +
30
+ // slack so a small/absent timeout_ms can't make the client give up before the
31
+ // bridge. Kept as a literal here (not imported from the C# side) because the
32
+ // bridge assembly isn't readable from the TS server; the cross-reference above
33
+ // is the contract — bump both together.
34
+ const BRIDGE_DEFAULT_TIMEOUT_MS = 30_000;
12
35
  const DIRECT_RESPONSE_TOOLS = new Set([
13
36
  "unity_open_mcp_validate_edit",
14
37
  "unity_open_mcp_checkpoint_create",
@@ -40,6 +63,45 @@ const DIRECT_RESPONSE_TOOLS = new Set([
40
63
  function sleep(ms) {
41
64
  return new Promise((resolve) => setTimeout(resolve, ms));
42
65
  }
66
+ /**
67
+ * Decide whether {@link LiveClient#postTool}'s catch block should re-POST after
68
+ * a transient fetch failure. Exported so the retry policy is unit-testable in
69
+ * isolation (driving a real response-timeout through postTool takes 40s because
70
+ * the fetch timeout is floored at BRIDGE_DEFAULT_TIMEOUT_MS + slack).
71
+ *
72
+ * undici (Node's fetch) throws a DISTINGUISHABLE error class for the two
73
+ * failure modes that reach this catch:
74
+ * - `TypeError` ("fetch failed") → CONNECTION failure: the socket never
75
+ * connected, was reset before the request body flushed, or the listen port
76
+ * is dead (ECONNREFUSED). No bytes reached the server → no Work was queued
77
+ * → no side effect is possible. Re-POSTing is ALWAYS safe, even on an
78
+ * unchanged endpoint (the same live bridge never received the first POST).
79
+ * - `AbortError` (AbortController timer) → RESPONSE timeout: the request
80
+ * connected and the server is still processing it. A side effect may have
81
+ * happened or be queued. Re-POSTing is only safe when the endpoint CHANGED
82
+ * during recovery (a restart → new bridge that never saw the original POST);
83
+ * on an unchanged endpoint the original call may still complete there, so
84
+ * re-POSTing would risk running the mutation a second time.
85
+ *
86
+ * This distinction matters because a burst of parallel /tools/* calls can leave
87
+ * a dead/reset connection in the pool: the next POST throws a TypeError on
88
+ * connect, and the old "unchanged endpoint → no retry" guard wedged the whole
89
+ * typed-editor surface even though the bridge was healthy and idle
90
+ * (specs/feedback.md entry 2026-07-02-b). The bug was that the guard conflated
91
+ * "endpoint unchanged" with "request in flight," which is only true when the
92
+ * fetch actually connected (AbortError), not when it failed to connect
93
+ * (TypeError).
94
+ */
95
+ export function shouldRetryPostAfterFailure(err, endpointChanged) {
96
+ // An endpoint change (Unity restart detected during recovery) always makes a
97
+ // retry safe regardless of failure class — the new bridge never saw the POST.
98
+ if (endpointChanged)
99
+ return true;
100
+ // Unchanged endpoint: only retry on a connection failure (TypeError). A
101
+ // response timeout (AbortError or anything else) on the same endpoint means
102
+ // the request may have reached the bridge → suppress the retry.
103
+ return err instanceof TypeError;
104
+ }
43
105
  const OFFLINE_HINT = "Ensure the Unity Editor is open with the Agent Bridge running. " +
44
106
  "The bridge port is per-project (20000 + sha256(projectPath) % 10000), not " +
45
107
  "fixed — if Unity is open the MCP server may be aimed at the wrong port. " +
@@ -74,6 +136,13 @@ export class LiveClient {
74
136
  dismissEnabled;
75
137
  dismissTimeoutMs;
76
138
  dismissIntervalMs;
139
+ /** M23 Plan 2 — active dialog policy (auto/manual/ignore/recover/safe-mode/
140
+ * cancel). Stored so waitForCompile can thread it into the dismiss loop
141
+ * without re-reading the env on every stall. */
142
+ dialogPolicy;
143
+ /** M23 Plan 2 — opt-in for the Project Upgrade Required dialog (irreversible,
144
+ * off by default). */
145
+ allowProjectUpgrade;
77
146
  /** M14 — per-session bearer token auto-discovered from the instance lock.
78
147
  * Undefined when no live lock was found (older bridge / env port override);
79
148
  * in that case no Authorization header is sent and the bridge must be in
@@ -87,20 +156,44 @@ export class LiveClient {
87
156
  /** One-shot guard so the version-compat warning (server vs bridge) is emitted
88
157
  * at most once per process — every /ping would otherwise re-warn. */
89
158
  compatWarned;
90
- constructor(port, pingCache, authToken, projectPath) {
159
+ /** M23 Plan 3 — env-overridable retry/compile-wait tunables. Resolved once
160
+ * at construction (the env does not change mid-process). Replaces the
161
+ * hardcoded MAX_COMPILE_WAIT_MS / COMPILE_POLL_INTERVAL_MS /
162
+ * TRANSIENT_RETRY_* module constants. */
163
+ retry;
164
+ /** M23 Plan 3 — agent identity sent as X-Agent-Id on every request so the
165
+ * bridge's fair round-robin queue can schedule across agents sharing one
166
+ * bridge. Defaults to the process-wide id; a transient LiveClient built for
167
+ * a per-request port override may carry a per-call id. */
168
+ agentId;
169
+ /** Parsed UNITY_OPEN_MCP_BRIDGE_PORT, or undefined. When set, the env
170
+ * override is authoritative and refreshEndpointFromLock() is a no-op (there
171
+ * is no lock to read — matches resolvePort/resolveAuthToken semantics).
172
+ * Stored so a mid-session refresh can respect the same override precedence
173
+ * the constructor used; without it a refresh would silently switch from an
174
+ * env-pinned port to the lock port. */
175
+ envPort;
176
+ constructor(port, pingCache, authToken, projectPath, agentId = PROCESS_AGENT_ID, envPort) {
91
177
  this.baseUrl = `http://127.0.0.1:${port}`;
92
178
  this.pingCache = pingCache;
93
179
  this.authToken = authToken;
94
180
  this.projectPath = projectPath;
95
- // M13 T4.5 launch-errors / Safe Mode dialog auto-dismissal. Resolved
96
- // once at construction; the env vars do not change mid-process. The
97
- // feature is enabled by default (Unity-MCP parity) and runs concurrently
98
- // with every compile/bridge readiness wait so it ticks on the same stall
99
- // points agents hit, not only at process spawn.
181
+ this.retry = readRetryTunables();
182
+ this.agentId = agentId;
183
+ this.envPort = envPort;
184
+ // M13 T4.5 + M23 Plan 2 startup dialog auto-dismissal. Resolved once at
185
+ // construction; the env vars do not change mid-process. The feature is
186
+ // enabled by default and runs concurrently with every compile/bridge
187
+ // readiness wait so it ticks on the same stall points agents hit, not
188
+ // only at process spawn. M23 Plan 2 generalizes T4.5's single Ignore
189
+ // click into the full 6-variant policy taxonomy (dialog-policy.ts) so
190
+ // different workflows can pick different buttons on the same dialog.
100
191
  const dismissCfg = readDismissConfig();
101
192
  this.dismissEnabled = dismissCfg.enabled;
102
193
  this.dismissTimeoutMs = dismissCfg.timeoutMs;
103
194
  this.dismissIntervalMs = dismissCfg.intervalMs;
195
+ this.dialogPolicy = dismissCfg.policy;
196
+ this.allowProjectUpgrade = dismissCfg.allowProjectUpgrade;
104
197
  this.compatWarned = false;
105
198
  }
106
199
  /**
@@ -176,7 +269,18 @@ export class LiveClient {
176
269
  const readyError = await this.ensureReady();
177
270
  if (readyError)
178
271
  return readyError;
179
- return this.postTool(toolName, args, true);
272
+ // M23 Plan 3 — compile-verify annotation. For compile-reload tools, capture
273
+ // a before-snapshot (bridge tool count + newest ScriptAssemblies DLL mtime)
274
+ // so the post-call detectCompileVerify() can flag a no-op/stale compile.
275
+ // Non-compile-reload tools skip this entirely (no extra /tools round-trip,
276
+ // no fs scan). The annotation is additive: it never blocks a success.
277
+ const isCompileReload = lifecycleFor(toolName).class === "compile-reload";
278
+ const before = isCompileReload ? await this.captureCompileSnapshot() : null;
279
+ const result = await this.postTool(toolName, args, true);
280
+ if (isCompileReload && !result.isError && before !== null) {
281
+ return this.annotateCompileVerify(toolName, result, before, args);
282
+ }
283
+ return result;
180
284
  }
181
285
  async handleRunTests(args) {
182
286
  const readyError = await this.ensureReady();
@@ -258,11 +362,19 @@ export class LiveClient {
258
362
  async postTool(toolName, args, retryOn503) {
259
363
  try {
260
364
  const timeoutMs = typeof args.timeout_ms === "number" ? args.timeout_ms : 60_000;
365
+ // Floor the client fetch timeout at the bridge's own default wait + slack
366
+ // so the client never aborts before the bridge has had a chance to return
367
+ // its timeout envelope. Without this floor, a small/absent timeout_ms
368
+ // (MCP hosts often omit the field → schema default 30000, or smaller)
369
+ // makes the client re-POST while the bridge is still processing →
370
+ // duplicate mutations (specs/feedback.md entry 4B). An explicit large
371
+ // timeout_ms still wins via the Math.max.
372
+ const fetchTimeout = Math.max(timeoutMs + 10_000, BRIDGE_DEFAULT_TIMEOUT_MS + 10_000);
261
373
  const res = await this.fetchWithTimeout(`/tools/${toolName}`, {
262
374
  method: "POST",
263
375
  headers: { "Content-Type": "application/json; charset=utf-8" },
264
376
  body: JSON.stringify(args),
265
- }, timeoutMs + 10_000);
377
+ }, fetchTimeout);
266
378
  if (res.status === 503 && retryOn503) {
267
379
  const compileError = await this.waitForCompile();
268
380
  if (compileError)
@@ -284,55 +396,113 @@ export class LiveClient {
284
396
  },
285
397
  });
286
398
  }
287
- if (DIRECT_RESPONSE_TOOLS.has(toolName)) {
288
- const directBody = (await res.json().catch(() => null));
289
- // M20 Plan 1 / T20.1.1 — capture_inline returns an `inlineImage` base64
290
- // PNG field. Unwrap it into an MCP image content block alongside a text
291
- // metadata block (matching the AnkleBreaker unity_graphics_*_capture
292
- // contract: [{type: image}, {type: text}]). The image carries the same
293
- // viewable payload a file-path screenshot would; the metadata keeps the
294
- // view / resolution / byteLength fields without the base64 blob.
295
- const inlineImage = directBody?.inlineImage;
296
- if (directBody != null &&
297
- typeof inlineImage === "string" &&
298
- inlineImage.length > 0 &&
299
- directBody.error == null) {
300
- const body = directBody;
301
- const mimeType = typeof body.mimeType === "string" ? body.mimeType : "image/png";
302
- const metadata = { ...body };
303
- delete metadata.inlineImage;
304
- return {
305
- content: [
306
- { type: "image", data: inlineImage, mimeType },
307
- { type: "text", text: JSON.stringify(metadata) },
308
- ],
309
- isError: false,
310
- };
311
- }
399
+ // The bridge returns TWO response shapes from /tools/*:
400
+ // 1. A MUTATION envelope: { mutation: { success, ... }, gate: {...} }
401
+ // for gate-tracked mutating tools (gameobject_*, assets_*, etc.).
402
+ // 2. A DIRECT body: the tool's own JSON (e.g. { status, count, tags })
403
+ // for read-only / state-only tools the bridge classifies as
404
+ // DirectResponseTools (editor_get_tags, asmdef_list, shader_list_all,
405
+ // scene_list_opened, material_get_properties, ~80 total).
406
+ //
407
+ // Previously postTool dispatched off a hardcoded DIRECT_RESPONSE_TOOLS set
408
+ // that was ~60 entries behind the bridge's set. Tools missing from it fell
409
+ // through to deriveIsError(body), which dereferences body.mutation.success
410
+ // — undefined for a direct body → TypeError. That thrown TypeError landed
411
+ // in the connection-recovery catch block, surfacing a misleading
412
+ // bridge_offline (and, with the entry-2026-07-02-b retry change, looping).
413
+ //
414
+ // The fix: detect the envelope shape from the BODY itself. A response with
415
+ // a top-level `mutation` object is a mutation envelope; anything else is a
416
+ // direct body. This is reliable because the bridge's DirectResponseTools
417
+ // path (HandleDirectResponseTool) never emits a `mutation` field and the
418
+ // gate path always does (BuildMutationEnvelope). The DIRECT_RESPONSE_TOOLS
419
+ // set is now only the inlineImage-unwrap allowlist (capture_inline), kept
420
+ // for that one special-case transform, not for shape routing.
421
+ const parsed = (await res.json().catch(() => null));
422
+ // capture_inline returns an `inlineImage` base64 PNG field. Unwrap it into
423
+ // an MCP image content block alongside a text metadata block
424
+ // ([{type: image}, {type: text}]). The image carries the same viewable
425
+ // payload a file-path screenshot would; the metadata keeps the view /
426
+ // resolution / byteLength fields without the base64 blob.
427
+ const inlineImage = parsed?.inlineImage;
428
+ if (DIRECT_RESPONSE_TOOLS.has(toolName) &&
429
+ parsed != null &&
430
+ typeof inlineImage === "string" &&
431
+ inlineImage.length > 0 &&
432
+ parsed.error == null) {
433
+ const mimeType = typeof parsed.mimeType === "string" ? parsed.mimeType : "image/png";
434
+ const metadata = { ...parsed };
435
+ delete metadata.inlineImage;
312
436
  return {
313
437
  content: [
314
- {
315
- type: "text",
316
- text: JSON.stringify(directBody ?? {}),
317
- },
438
+ { type: "image", data: inlineImage, mimeType },
439
+ { type: "text", text: JSON.stringify(metadata) },
318
440
  ],
319
- isError: directBody?.error != null,
441
+ isError: false,
320
442
  };
321
443
  }
322
- const body = (await res.json());
444
+ // Mutation envelope vs direct body, decided from the body itself.
445
+ if (parsed != null && typeof parsed.mutation === "object" && parsed.mutation !== null) {
446
+ const body = parsed;
447
+ return {
448
+ content: [{ type: "text", text: JSON.stringify(body) }],
449
+ isError: deriveIsError(body),
450
+ };
451
+ }
452
+ // Direct body: return it verbatim. isError when the tool reported a
453
+ // top-level error field (the bridge's direct-response convention).
323
454
  return {
324
- content: [{ type: "text", text: JSON.stringify(body) }],
325
- isError: deriveIsError(body),
455
+ content: [
456
+ {
457
+ type: "text",
458
+ text: JSON.stringify(parsed ?? {}),
459
+ },
460
+ ],
461
+ isError: parsed?.error != null,
326
462
  };
327
463
  }
328
- catch {
464
+ catch (err) {
465
+ // M20 Plan 4-5 / T-fix-2 — transient POST failure (ECONNREFUSED /
466
+ // timeout during a domain reload). Classify + recover before declaring
467
+ // offline. handleTransientOffline re-syncs the cached endpoint from the
468
+ // lock first (so a Unity restart self-heals).
469
+ //
470
+ // Re-POST discipline (specs/feedback.md entry 4A — duplicate side-effects):
471
+ // only re-POST when the endpoint CHANGED during recovery (a restart → new
472
+ // bridge that never saw the original POST, so a retry is safe). When the
473
+ // endpoint is unchanged the same live bridge is still processing — or has
474
+ // queued — the original POST's Work; re-POSTing would run the mutation a
475
+ // second time. In that case surface the timeout instead of retrying.
476
+ //
477
+ // shouldRetryPostAfterFailure additionally allows a re-POST on a genuine
478
+ // CONNECTION failure (TypeError — the socket never connected / was reset
479
+ // before the body flushed / ECONNREFUSED), because no bytes reached the
480
+ // server and no side effect is possible. This must NOT fire for a
481
+ // TypeError thrown by response parsing (e.g. deriveIsError on a malformed
482
+ // body) — that path is now guarded by parsing the body defensively above,
483
+ // but as a belt-and-braces measure the response-parsing path is structured
484
+ // so it cannot throw into this catch (every .json() is .catch()-guarded).
485
+ const endpointBefore = this.baseUrl;
486
+ const recovered = await this.handleTransientOffline("post");
487
+ if (recovered !== null)
488
+ return recovered;
489
+ const endpointChanged = this.baseUrl !== endpointBefore;
490
+ if (shouldRetryPostAfterFailure(err, endpointChanged)) {
491
+ return this.postTool(toolName, args, retryOn503);
492
+ }
329
493
  return makeErrorResult({
330
494
  code: "bridge_offline",
331
- message: `Failed to reach bridge at ${this.baseUrl}. ${buildOfflineHint(this.projectPath)}`,
495
+ message: `Tool '${toolName}' did not return from the bridge within the client ` +
496
+ `timeout. The bridge endpoint did not change (no restart detected) and ` +
497
+ `the failure was a response timeout (not a connection failure), so ` +
498
+ `the request was NOT retried — re-POSTing would risk running the ` +
499
+ `mutation twice on the same bridge. If the editor was briefly slow ` +
500
+ `(unfocused, GC, a long main-thread op), the original call may still ` +
501
+ `complete there; verify the effect before retrying. Endpoint: ${this.baseUrl}.`,
332
502
  detail: {
333
503
  error: {
334
504
  code: "bridge_offline",
335
- message: `Cannot connect to bridge at ${this.baseUrl}`,
505
+ message: `Client timeout against unchanged endpoint ${this.baseUrl}; not retried to avoid duplicate side-effects.`,
336
506
  },
337
507
  },
338
508
  });
@@ -371,14 +541,42 @@ export class LiveClient {
371
541
  }
372
542
  catch {
373
543
  // Bridge is unreachable (ECONNREFUSED / timeout). Before falling back to
374
- // the generic offline error, check whether the instance lock indicates a
375
- // DEAD bridge assembly the Unity process is still alive but the bridge
376
- // failed to reload after a compile error. That case is NOT recoverable
377
- // by waiting; surface it immediately so the agent can fetch the compiler
378
- // errors and fix them instead of hanging on /ping.
379
- const deadBridge = this.deadBridgeResult();
380
- if (deadBridge)
381
- return deadBridge;
544
+ // the generic offline error, classify the failure via the instance lock:
545
+ // - dead_bridge → bridge assembly failed to recompile (fail fast)
546
+ // - reloading/compiling a normal domain reload is in flight; wait it
547
+ // out (the listener socket is torn down for the reload duration) then
548
+ // re-probe once before declaring offline (T-fix-2)
549
+ // - otherwise → retry with backoff a few times, then offline
550
+ // handleTransientOffline refreshes the cached endpoint from the lock
551
+ // first, so a Unity restart (new PID/port) re-points this client before
552
+ // the retry probes (specs/feedback.md entry 2).
553
+ const recovered = await this.handleTransientOffline("ping");
554
+ if (recovered !== null)
555
+ return recovered;
556
+ // Recovery returned null ("recovered" — a /ping probe succeeded during
557
+ // handleTransientOffline, possibly against a freshly-refreshed endpoint
558
+ // after a restart). Re-probe /ping ONCE on the (possibly new) endpoint
559
+ // before declaring offline: without this, a restart that refreshed the
560
+ // endpoint mid-recovery would still surface bridge_offline here, and the
561
+ // caller (handleToolCall) treats any non-null ensureReady result as a
562
+ // terminal error — so the next tool call would fail despite the bridge
563
+ // being back. The single re-probe closes that gap.
564
+ try {
565
+ const retryRes = await this.fetchWithTimeout("/ping", { method: "GET" });
566
+ if (retryRes.status === 503)
567
+ return this.waitForCompile();
568
+ if (retryRes.ok) {
569
+ const retryBody = (await retryRes.json());
570
+ this.pingCache.record(retryBody);
571
+ if (retryBody.compiling)
572
+ return this.waitForCompile();
573
+ if (retryBody.connected)
574
+ return null; // recovered against the new endpoint
575
+ }
576
+ }
577
+ catch {
578
+ // Re-probe also failed — fall through to the offline error below.
579
+ }
382
580
  return makeErrorResult({
383
581
  code: "bridge_offline",
384
582
  message: `Bridge is not reachable at ${this.baseUrl}. ${buildOfflineHint(this.projectPath)}`,
@@ -391,6 +589,157 @@ export class LiveClient {
391
589
  });
392
590
  }
393
591
  }
592
+ /**
593
+ * M20 Plan 4-5 / T-fix-2 — classify a transient /ping or tool-POST failure
594
+ * (ECONNREFUSED / timeout / socket reset) via the instance lock and either
595
+ * recover or surface the right error.
596
+ *
597
+ * Returns:
598
+ * - a structured `bridge_compile_failed` result when the lock shows a dead
599
+ * bridge assembly (fail fast — preserves the pre-existing fast-path),
600
+ * - `null` to signal "recovered — the caller should retry its operation"
601
+ * after either waiting out a `reloading`/`compiling` lock window or
602
+ * succeeding a bounded backoff retry,
603
+ * - a `bridge_offline` result when the bridge is genuinely unreachable after
604
+ * the recovery attempts (so the caller's outer `bridge_offline` fallback
605
+ * is reached only via this returned result).
606
+ *
607
+ * `operation` is "ping" | "post" and only affects the log line — both paths
608
+ * share the same recovery logic.
609
+ */
610
+ async handleTransientOffline(operation) {
611
+ // Before any classification/retry, re-sync the cached endpoint to the
612
+ // current live bridge. A Unity restart writes a fresh PID/port/authToken
613
+ // to the lock; without this refresh every retry below would keep hitting
614
+ // the dead cached listener (specs/feedback.md entry 2) and, on the POST
615
+ // path, re-dispatch the Work to the new bridge (duplicate side-effects,
616
+ // entry 4A). refreshEndpointFromLock is a no-op when an env-port override
617
+ // is in force or nothing has changed.
618
+ this.refreshEndpointFromLock();
619
+ // Dead bridge assembly — not recoverable by waiting. Fail fast so the
620
+ // agent can fetch compile errors instead of hanging on /ping.
621
+ const deadBridge = this.deadBridgeResult();
622
+ if (deadBridge)
623
+ return deadBridge;
624
+ const classification = this.classifyLockNow();
625
+ // A normal domain reload in flight: the listener socket is torn down for
626
+ // the reload duration (BridgeHttpServer.OnBeforeAssemblyReload →
627
+ // _listener.Stop()). classifyInstance folds both lock states
628
+ // "reloading" and "compiling" into the "reloading" classification
629
+ // (instance-discovery.ts) — so a single check covers the whole compile +
630
+ // reload window. Wait it out, then signal the caller to retry.
631
+ if (classification === "reloading") {
632
+ const waitError = await this.waitForCompile();
633
+ if (waitError)
634
+ return waitError; // timed out or dead_bridge mid-wait
635
+ return null; // recovered — caller retries the original operation
636
+ }
637
+ // No reload signal but a transient refusal/reset/timeout. Retry a bounded
638
+ // number of times with backoff before declaring offline. Covers brief
639
+ // socket churn that isn't reflected in the lock yet. The bounds come from
640
+ // the env-overridable retry tunables (M23 Plan 3 / retry-policy.ts).
641
+ const maxAttempts = this.retry.transientRetryAttempts;
642
+ const backoffMs = this.retry.transientBackoffMs;
643
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
644
+ await sleep(backoffMs * attempt);
645
+ // Re-classify on each attempt: a reload may have begun between the
646
+ // first failure and now.
647
+ const reclass = this.classifyLockNow();
648
+ if (reclass === "dead_bridge")
649
+ return this.deadBridgeResult();
650
+ if (reclass === "reloading") {
651
+ const waitError = await this.waitForCompile();
652
+ if (waitError)
653
+ return waitError;
654
+ return null;
655
+ }
656
+ // Probe whether the listener is back up.
657
+ try {
658
+ const res = await this.fetchWithTimeout("/ping", { method: "GET" });
659
+ if (res.ok || res.status === 503) {
660
+ // Listener responded — recovered. Caller re-runs its readiness /
661
+ // POST. (503 still counts: it means compiling, handled on retry.)
662
+ return null;
663
+ }
664
+ }
665
+ catch {
666
+ // Still down — loop and back off again.
667
+ }
668
+ }
669
+ void operation; // log-only parameter, reserved for future telemetry
670
+ const retryMsg = `Bridge is not reachable at ${this.baseUrl} after ${maxAttempts} retries. ${buildOfflineHint(this.projectPath)}`;
671
+ return makeErrorResult({
672
+ code: "bridge_offline",
673
+ message: retryMsg,
674
+ detail: {
675
+ error: {
676
+ code: "bridge_offline",
677
+ message: retryMsg,
678
+ },
679
+ },
680
+ });
681
+ }
682
+ /**
683
+ * Re-resolve the bridge endpoint (port + authToken) from the on-disk instance
684
+ * lock and update the cached {@link baseUrl} / {@link authToken} when the
685
+ * live bridge has moved. Returns true when the endpoint changed.
686
+ *
687
+ * This is the single fix for two coupled failure modes (specs/feedback.md
688
+ * entries 2 + 4A): the client cached the port/token once at construction,
689
+ * so a Unity restart (new PID/port/authToken in the lock) left every retry
690
+ * hitting a dead listener — producing minutes of `bridge_offline` *and*
691
+ * (because {@link postTool} re-POSTs on recovery) duplicate side-effects
692
+ * once the editor came back. Refreshing here lets a restart self-heal.
693
+ *
694
+ * No-op (returns false) when there is no projectPath, an env-port override
695
+ * is in force (env is authoritative — no lock to read, matching
696
+ * resolvePort/resolveAuthToken), or the lock is missing/its PID is dead/
697
+ * nothing actually changed. Never throws — readInstanceLock/isPidAlive are
698
+ * already fault-tolerant.
699
+ */
700
+ refreshEndpointFromLock() {
701
+ if (!this.projectPath)
702
+ return false;
703
+ if (typeof this.envPort === "number")
704
+ return false; // env override wins
705
+ let lock;
706
+ try {
707
+ lock = readInstanceLock(this.projectPath);
708
+ }
709
+ catch {
710
+ return false;
711
+ }
712
+ if (!lock)
713
+ return false;
714
+ if (!isPidAlive(lock.pid))
715
+ return false;
716
+ const portChanged = typeof lock.port === "number" &&
717
+ lock.port > 0 &&
718
+ this.baseUrl !== `http://127.0.0.1:${lock.port}`;
719
+ const tokenChanged = lock.authToken !== this.authToken;
720
+ if (!portChanged && !tokenChanged)
721
+ return false;
722
+ if (portChanged)
723
+ this.baseUrl = `http://127.0.0.1:${lock.port}`;
724
+ if (tokenChanged)
725
+ this.authToken = lock.authToken;
726
+ return true;
727
+ }
728
+ /**
729
+ * Read + classify the instance lock at the current moment, without throwing.
730
+ * Returns "gone" (treated as no-signal) when the lock can't be read or the
731
+ * project path is unknown. T-fix-2 helper.
732
+ */
733
+ classifyLockNow() {
734
+ if (!this.projectPath)
735
+ return "gone";
736
+ try {
737
+ return classifyInstance(readInstanceLock(this.projectPath));
738
+ }
739
+ catch {
740
+ return "gone";
741
+ }
742
+ }
394
743
  // Detect the dead-bridge-assembly signature from the on-disk instance lock:
395
744
  // Unity process alive (PID running) but heartbeat stale (the bridge's
396
745
  // [InitializeOnLoad] never re-ran after a compile failure, so the heartbeat
@@ -433,14 +782,15 @@ export class LiveClient {
433
782
  };
434
783
  }
435
784
  async waitForCompile() {
436
- const deadline = Date.now() + MAX_COMPILE_WAIT_MS;
785
+ const compileWaitMs = this.retry.compileWaitMs;
786
+ const deadline = Date.now() + compileWaitMs;
437
787
  // M13 T4.5 — run the launch-errors / Safe Mode dialog auto-dismiss loop
438
788
  // CONCURRENTLY with the compile poll. The dismiss loop ticks on the same
439
- // stall point this method represents (UCP bridge-wait pattern): if a
440
- // native modal is blocking the Editor, the compile poll below will spin
441
- // until timeout with no recovery. The dismiss loop clicks Ignore on that
442
- // modal; the moment compile resolves (compiling → idle), we abort the
443
- // loop — there is no launch dialog left to dismiss once Unity is idle.
789
+ // stall point this method represents: if a native modal is blocking the
790
+ // Editor, the compile poll below will spin until timeout with no recovery.
791
+ // The dismiss loop clicks Ignore on that modal; the moment compile
792
+ // resolves (compiling → idle), we abort the loop — there is no launch
793
+ // dialog left to dismiss once Unity is idle.
444
794
  //
445
795
  // When auto-dismiss is opted out (UNITY_OPEN_MCP_NO_AUTO_DISMISS_LAUNCH_ERRORS=1),
446
796
  // `dismissEnabled` is false and this branch is skipped entirely,
@@ -452,13 +802,15 @@ export class LiveClient {
452
802
  const dismissOpts = {
453
803
  timeoutMs: this.dismissTimeoutMs,
454
804
  intervalMs: this.dismissIntervalMs,
805
+ policy: this.dialogPolicy,
806
+ allowProjectUpgrade: this.allowProjectUpgrade,
455
807
  abortSignal: dismissAbort.signal,
456
808
  };
457
809
  dismissDone = this.runDismissLoop(dismissOpts);
458
810
  }
459
811
  try {
460
812
  while (Date.now() < deadline) {
461
- await sleep(COMPILE_POLL_INTERVAL_MS);
813
+ await sleep(this.retry.compilePollIntervalMs);
462
814
  try {
463
815
  const res = await this.fetchWithTimeout("/ping", { method: "GET" });
464
816
  if (res.status === 503)
@@ -489,12 +841,12 @@ export class LiveClient {
489
841
  return deadBridge;
490
842
  return makeErrorResult({
491
843
  code: "compile_timeout",
492
- message: `Unity is still compiling after ${MAX_COMPILE_WAIT_MS / 1000}s. ` +
844
+ message: `Unity is still compiling after ${compileWaitMs / 1000}s. ` +
493
845
  "The compile-wait timeout was exceeded.",
494
846
  detail: {
495
847
  error: {
496
848
  code: "compile_timeout",
497
- message: `Compile-wait exceeded ${MAX_COMPILE_WAIT_MS / 1000}s`,
849
+ message: `Compile-wait exceeded ${compileWaitMs / 1000}s`,
498
850
  },
499
851
  },
500
852
  });
@@ -507,13 +859,13 @@ export class LiveClient {
507
859
  }
508
860
  }
509
861
  /**
510
- * Run the launch-errors dismiss polling loop. Indirected through a method
862
+ * Run the startup-dialog dismiss polling loop. Indirected through a method
511
863
  * so unit tests can stub the OS-clicking loop without invoking
512
864
  * PowerShell / osascript / xdotool. Production uses the real
513
- * `pollAndDismissLaunchErrors`.
865
+ * `pollAndDismissDialogs`.
514
866
  */
515
867
  runDismissLoop(opts) {
516
- return pollAndDismissLaunchErrors(opts);
868
+ return pollAndDismissDialogs(opts);
517
869
  }
518
870
  async readResource(route) {
519
871
  try {
@@ -563,6 +915,105 @@ export class LiveClient {
563
915
  return null;
564
916
  }
565
917
  }
918
+ // -------------------------------------------------------------------------
919
+ // M23 Plan 3 — compile-verify annotation (compile_noop / dll_stale).
920
+ //
921
+ // For compile-reload tools we capture a before/after snapshot and let the
922
+ // pure detectCompileVerify() flag a no-op or stale compile. The annotation
923
+ // is additive (`_compileVerify` on the result body) — it never blocks a
924
+ // successful response, only surfaces a structured signal so an agent can
925
+ // branch instead of trusting a no-op success. Snapshots degrade gracefully:
926
+ // if the bridge inventory or DLL mtimes can't be read, the fields are
927
+ // undefined and the detector returns null (no false positive).
928
+ // -------------------------------------------------------------------------
929
+ /**
930
+ * Capture a compile-verify snapshot: the bridge tool inventory count + the
931
+ * newest mtime under Library/ScriptAssemblies. Returns undefined fields
932
+ * (never throws) when the bridge or filesystem is unavailable.
933
+ */
934
+ async captureCompileSnapshot() {
935
+ const snap = {};
936
+ try {
937
+ const inventory = await this.listBridgeTools();
938
+ if (inventory)
939
+ snap.bridgeToolCount = inventory.tools.size;
940
+ }
941
+ catch {
942
+ // best-effort — undefined count is a valid "unknown" snapshot
943
+ }
944
+ const dllMtime = this.newestScriptAssembliesMtime();
945
+ if (dllMtime !== undefined)
946
+ snap.dllMtimeMs = dllMtime;
947
+ return snap;
948
+ }
949
+ /**
950
+ * Newest mtime (epoch ms) of `Library/ScriptAssemblies/*.dll` for this
951
+ * project, or undefined when the directory is missing/unreadable. Used by
952
+ * the compile-verify detector to tell whether a recompile actually advanced
953
+ * the compiled output.
954
+ */
955
+ newestScriptAssembliesMtime() {
956
+ if (!this.projectPath)
957
+ return undefined;
958
+ const dir = join(this.projectPath, "Library", "ScriptAssemblies");
959
+ let entries;
960
+ try {
961
+ entries = readdirSync(dir);
962
+ }
963
+ catch {
964
+ return undefined;
965
+ }
966
+ let newest;
967
+ for (const name of entries) {
968
+ if (!name.endsWith(".dll"))
969
+ continue;
970
+ try {
971
+ const m = statSync(join(dir, name)).mtimeMs;
972
+ if (newest === undefined || m > newest)
973
+ newest = m;
974
+ }
975
+ catch {
976
+ // best-effort per file
977
+ }
978
+ }
979
+ return newest;
980
+ }
981
+ /**
982
+ * Apply the compile-verify annotation to a successful compile-reload result.
983
+ * Reads the after-snapshot, runs the pure detector, and — when flagged —
984
+ * injects `_compileVerify: { code, recommendation }` into the result body.
985
+ * The source-edit mtime is inferred from `args._sourceMtimeMs` when the
986
+ * caller (e.g. script_write) supplied it; otherwise only the no-op path
987
+ * (count + dll-mtime delta) is evaluated.
988
+ */
989
+ async annotateCompileVerify(_toolName, result, before, args) {
990
+ const after = await this.captureCompileSnapshot();
991
+ const sourceMtimeMs = typeof args._sourceMtimeMs === "number"
992
+ ? args._sourceMtimeMs
993
+ : undefined;
994
+ const detection = detectCompileVerify({ before, after, sourceMtimeMs });
995
+ const annotation = buildCompileVerifyAnnotation(detection);
996
+ if (annotation === null)
997
+ return result;
998
+ // Inject into the first text content block's JSON body. Mirrors the
999
+ // injectRouteMeta shape in tool-router.ts.
1000
+ const textIndex = result.content.findIndex((c) => c.type === "text");
1001
+ if (textIndex < 0)
1002
+ return result;
1003
+ const block = result.content[textIndex];
1004
+ if (block.type !== "text")
1005
+ return result;
1006
+ try {
1007
+ const body = JSON.parse(block.text);
1008
+ body._compileVerify = annotation;
1009
+ const newContent = result.content.slice();
1010
+ newContent[textIndex] = { type: "text", text: JSON.stringify(body) };
1011
+ return { ...result, content: newContent };
1012
+ }
1013
+ catch {
1014
+ return result;
1015
+ }
1016
+ }
566
1017
  fetchWithTimeout(path, init, timeoutMs = PING_TIMEOUT_MS) {
567
1018
  const controller = new AbortController();
568
1019
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -573,6 +1024,12 @@ export class LiveClient {
573
1024
  if (this.authToken && !headers.has("Authorization")) {
574
1025
  headers.set("Authorization", `Bearer ${this.authToken}`);
575
1026
  }
1027
+ // M23 Plan 3 — attach the agent identity so the bridge's fair round-robin
1028
+ // queue can schedule across agents sharing one bridge. Merge so a caller-
1029
+ // supplied header (rare) wins.
1030
+ if (this.agentId && !headers.has("X-Agent-Id")) {
1031
+ headers.set("X-Agent-Id", this.agentId);
1032
+ }
576
1033
  return fetch(`${this.baseUrl}${path}`, {
577
1034
  ...init,
578
1035
  headers,