@imgly/codesign-mcp 0.1.6 → 0.1.8

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 (448) hide show
  1. package/README.md +5 -10
  2. package/assets/docs/api/index.d.ts +9 -3
  3. package/assets/docs/guide/SKILL.md +2 -2
  4. package/assets/docs/guide/api/node/classes/creativeengine.md +13 -13
  5. package/assets/docs/guide/edit-image/transform/scale.md +1 -3
  6. package/assets/docs/guide/filters-and-effects/duotone.md +1 -1
  7. package/assets/docs/guide/get-started/bun.md +0 -2
  8. package/assets/docs/guide/get-started/deno.md +0 -1
  9. package/assets/docs/guide/get-started/vanilla.md +0 -2
  10. package/assets/docs/guide/import-media/default-assets.md +20 -24
  11. package/assets/docs/guide/plugins/print-ready-pdf.md +0 -2
  12. package/assets/docs/guide/serve-assets.md +80 -23
  13. package/assets/docs/handbook/SKILL.md +6 -7
  14. package/dist/assets/inline.generated.js +12 -12
  15. package/dist/assets/inline.generated.js.map +1 -1
  16. package/dist/cli/http.d.ts.map +1 -1
  17. package/dist/cli/http.js +29 -5
  18. package/dist/cli/http.js.map +1 -1
  19. package/dist/cli/stdio.d.ts.map +1 -1
  20. package/dist/cli/stdio.js +145 -48
  21. package/dist/cli/stdio.js.map +1 -1
  22. package/dist/cli/trial-license.generated.d.ts +1 -1
  23. package/dist/cli/trial-license.generated.js +1 -1
  24. package/dist/cli/trial-license.generated.js.map +1 -1
  25. package/dist/config.d.ts +17 -2
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +20 -1
  28. package/dist/config.js.map +1 -1
  29. package/dist/diagnostics/bundle.d.ts +3 -0
  30. package/dist/diagnostics/bundle.d.ts.map +1 -1
  31. package/dist/diagnostics/bundle.js +25 -22
  32. package/dist/diagnostics/bundle.js.map +1 -1
  33. package/dist/diagnostics/events.d.ts +6 -0
  34. package/dist/diagnostics/events.d.ts.map +1 -1
  35. package/dist/diagnostics/events.js.map +1 -1
  36. package/dist/diagnostics/prompt.d.ts +3 -0
  37. package/dist/diagnostics/prompt.d.ts.map +1 -0
  38. package/dist/diagnostics/prompt.js +21 -0
  39. package/dist/diagnostics/prompt.js.map +1 -0
  40. package/dist/diagnostics/router.d.ts +12 -0
  41. package/dist/diagnostics/router.d.ts.map +1 -0
  42. package/dist/diagnostics/router.js +64 -0
  43. package/dist/diagnostics/router.js.map +1 -0
  44. package/dist/diagnostics/spa-assets.generated.d.ts.map +1 -0
  45. package/dist/diagnostics/spa-assets.generated.js +1231 -0
  46. package/dist/diagnostics/spa-assets.generated.js.map +1 -0
  47. package/dist/diagnostics/status.d.ts +38 -0
  48. package/dist/diagnostics/status.d.ts.map +1 -0
  49. package/dist/diagnostics/status.js +53 -0
  50. package/dist/diagnostics/status.js.map +1 -0
  51. package/dist/diagnostics/summary.d.ts +48 -0
  52. package/dist/diagnostics/summary.d.ts.map +1 -0
  53. package/dist/diagnostics/summary.js +116 -0
  54. package/dist/diagnostics/summary.js.map +1 -0
  55. package/dist/diagnostics/tool.d.ts +16 -0
  56. package/dist/diagnostics/tool.d.ts.map +1 -0
  57. package/dist/diagnostics/tool.js +90 -0
  58. package/dist/diagnostics/tool.js.map +1 -0
  59. package/dist/engine/asset-sources.d.ts +8 -0
  60. package/dist/engine/asset-sources.d.ts.map +1 -1
  61. package/dist/engine/asset-sources.js +9 -2
  62. package/dist/engine/asset-sources.js.map +1 -1
  63. package/dist/engine/cesdk-adapter.d.ts +5 -1
  64. package/dist/engine/cesdk-adapter.d.ts.map +1 -1
  65. package/dist/engine/cesdk-adapter.js +8 -1
  66. package/dist/engine/cesdk-adapter.js.map +1 -1
  67. package/dist/engine/engine-capacity.d.ts +16 -0
  68. package/dist/engine/engine-capacity.d.ts.map +1 -0
  69. package/dist/engine/engine-capacity.js +48 -0
  70. package/dist/engine/engine-capacity.js.map +1 -0
  71. package/dist/engine/engine-slots.d.ts +48 -0
  72. package/dist/engine/engine-slots.d.ts.map +1 -0
  73. package/dist/engine/engine-slots.js +182 -0
  74. package/dist/engine/engine-slots.js.map +1 -0
  75. package/dist/engine/uri-resolver.js.map +1 -1
  76. package/dist/server.d.ts +25 -0
  77. package/dist/server.d.ts.map +1 -1
  78. package/dist/server.js +24 -4
  79. package/dist/server.js.map +1 -1
  80. package/dist/tools/engine.d.ts +92 -14
  81. package/dist/tools/engine.d.ts.map +1 -1
  82. package/dist/tools/engine.js +147 -20
  83. package/dist/tools/engine.js.map +1 -1
  84. package/dist/tools/export.d.ts.map +1 -1
  85. package/dist/tools/export.js +34 -4
  86. package/dist/tools/export.js.map +1 -1
  87. package/dist/tools/index.js +2 -2
  88. package/dist/tools/index.js.map +1 -1
  89. package/dist/tools/load.d.ts.map +1 -1
  90. package/dist/tools/load.js +26 -19
  91. package/dist/tools/load.js.map +1 -1
  92. package/dist/tools/preview.d.ts.map +1 -1
  93. package/dist/tools/preview.js +60 -40
  94. package/dist/tools/preview.js.map +1 -1
  95. package/dist/tools/{open-editor.d.ts → view.d.ts} +3 -3
  96. package/dist/tools/view.d.ts.map +1 -0
  97. package/dist/tools/{open-editor.js → view.js} +20 -18
  98. package/dist/tools/view.js.map +1 -0
  99. package/dist/viewer/listener.d.ts +2 -0
  100. package/dist/viewer/listener.d.ts.map +1 -1
  101. package/dist/viewer/listener.js +2 -0
  102. package/dist/viewer/listener.js.map +1 -1
  103. package/dist/viewer/presigned-r2-url-builder.d.ts +1 -1
  104. package/dist/viewer/presigned-r2-url-builder.js +1 -1
  105. package/dist/viewer/presigned-r2-url-builder.js.map +1 -1
  106. package/dist/viewer/router.d.ts.map +1 -1
  107. package/dist/viewer/router.js +8 -17
  108. package/dist/viewer/router.js.map +1 -1
  109. package/dist/viewer/session-mount.js.map +1 -1
  110. package/dist/viewer/spa-assets.generated.d.ts.map +1 -1
  111. package/dist/viewer/spa-assets.generated.js +750 -869
  112. package/dist/viewer/spa-assets.generated.js.map +1 -1
  113. package/dist/viewer/url-builder.d.ts +4 -3
  114. package/dist/viewer/url-builder.d.ts.map +1 -1
  115. package/dist/viewer/url-builder.js +4 -4
  116. package/dist/viewer/url-builder.js.map +1 -1
  117. package/package.json +7 -6
  118. package/dist/dashboard/router.d.ts +0 -3
  119. package/dist/dashboard/router.d.ts.map +0 -1
  120. package/dist/dashboard/router.js +0 -19
  121. package/dist/dashboard/router.js.map +0 -1
  122. package/dist/dashboard/spa-assets.generated.d.ts.map +0 -1
  123. package/dist/dashboard/spa-assets.generated.js +0 -220
  124. package/dist/dashboard/spa-assets.generated.js.map +0 -1
  125. package/dist/metrics/mount.d.ts +0 -11
  126. package/dist/metrics/mount.d.ts.map +0 -1
  127. package/dist/metrics/mount.js +0 -13
  128. package/dist/metrics/mount.js.map +0 -1
  129. package/dist/metrics/recorder.d.ts +0 -55
  130. package/dist/metrics/recorder.d.ts.map +0 -1
  131. package/dist/metrics/recorder.js +0 -97
  132. package/dist/metrics/recorder.js.map +0 -1
  133. package/dist/test/scripts/changelog-add.d.ts +0 -8
  134. package/dist/test/scripts/changelog-add.d.ts.map +0 -1
  135. package/dist/test/scripts/changelog-add.js +0 -50
  136. package/dist/test/scripts/changelog-add.js.map +0 -1
  137. package/dist/test/scripts/changelog-collate.d.ts +0 -38
  138. package/dist/test/scripts/changelog-collate.d.ts.map +0 -1
  139. package/dist/test/scripts/changelog-collate.js +0 -171
  140. package/dist/test/scripts/changelog-collate.js.map +0 -1
  141. package/dist/test/test/changelog.test.d.ts +0 -2
  142. package/dist/test/test/changelog.test.d.ts.map +0 -1
  143. package/dist/test/test/changelog.test.js +0 -293
  144. package/dist/test/test/changelog.test.js.map +0 -1
  145. package/dist/test/test/cli/dispatcher.test.d.ts +0 -2
  146. package/dist/test/test/cli/dispatcher.test.d.ts.map +0 -1
  147. package/dist/test/test/cli/dispatcher.test.js +0 -54
  148. package/dist/test/test/cli/dispatcher.test.js.map +0 -1
  149. package/dist/test/test/cli/redirect-console.test.d.ts +0 -2
  150. package/dist/test/test/cli/redirect-console.test.d.ts.map +0 -1
  151. package/dist/test/test/cli/redirect-console.test.js +0 -78
  152. package/dist/test/test/cli/redirect-console.test.js.map +0 -1
  153. package/dist/test/test/cli/resolve-license.test.d.ts +0 -2
  154. package/dist/test/test/cli/resolve-license.test.d.ts.map +0 -1
  155. package/dist/test/test/cli/resolve-license.test.js +0 -29
  156. package/dist/test/test/cli/resolve-license.test.js.map +0 -1
  157. package/dist/test/test/cli/stdio-diagnostics.test.d.ts +0 -2
  158. package/dist/test/test/cli/stdio-diagnostics.test.d.ts.map +0 -1
  159. package/dist/test/test/cli/stdio-diagnostics.test.js +0 -31
  160. package/dist/test/test/cli/stdio-diagnostics.test.js.map +0 -1
  161. package/dist/test/test/cli/stdio-license.test.d.ts +0 -2
  162. package/dist/test/test/cli/stdio-license.test.d.ts.map +0 -1
  163. package/dist/test/test/cli/stdio-license.test.js +0 -19
  164. package/dist/test/test/cli/stdio-license.test.js.map +0 -1
  165. package/dist/test/test/config.test.d.ts +0 -2
  166. package/dist/test/test/config.test.d.ts.map +0 -1
  167. package/dist/test/test/config.test.js +0 -95
  168. package/dist/test/test/config.test.js.map +0 -1
  169. package/dist/test/test/cross-ref.test.d.ts +0 -2
  170. package/dist/test/test/cross-ref.test.d.ts.map +0 -1
  171. package/dist/test/test/cross-ref.test.js +0 -39
  172. package/dist/test/test/cross-ref.test.js.map +0 -1
  173. package/dist/test/test/diagnostics/bundle.test.d.ts +0 -2
  174. package/dist/test/test/diagnostics/bundle.test.d.ts.map +0 -1
  175. package/dist/test/test/diagnostics/bundle.test.js +0 -53
  176. package/dist/test/test/diagnostics/bundle.test.js.map +0 -1
  177. package/dist/test/test/diagnostics/compose.test.d.ts +0 -2
  178. package/dist/test/test/diagnostics/compose.test.d.ts.map +0 -1
  179. package/dist/test/test/diagnostics/compose.test.js +0 -31
  180. package/dist/test/test/diagnostics/compose.test.js.map +0 -1
  181. package/dist/test/test/diagnostics/file-sink.test.d.ts +0 -2
  182. package/dist/test/test/diagnostics/file-sink.test.d.ts.map +0 -1
  183. package/dist/test/test/diagnostics/file-sink.test.js +0 -113
  184. package/dist/test/test/diagnostics/file-sink.test.js.map +0 -1
  185. package/dist/test/test/diagnostics/redact.test.d.ts +0 -2
  186. package/dist/test/test/diagnostics/redact.test.d.ts.map +0 -1
  187. package/dist/test/test/diagnostics/redact.test.js +0 -59
  188. package/dist/test/test/diagnostics/redact.test.js.map +0 -1
  189. package/dist/test/test/diagnostics/tool-call-adapter.test.d.ts +0 -2
  190. package/dist/test/test/diagnostics/tool-call-adapter.test.d.ts.map +0 -1
  191. package/dist/test/test/diagnostics/tool-call-adapter.test.js +0 -34
  192. package/dist/test/test/diagnostics/tool-call-adapter.test.js.map +0 -1
  193. package/dist/test/test/engine/asset-sources.test.d.ts +0 -2
  194. package/dist/test/test/engine/asset-sources.test.d.ts.map +0 -1
  195. package/dist/test/test/engine/asset-sources.test.js +0 -100
  196. package/dist/test/test/engine/asset-sources.test.js.map +0 -1
  197. package/dist/test/test/engine/cesdk-adapter.test.d.ts +0 -2
  198. package/dist/test/test/engine/cesdk-adapter.test.d.ts.map +0 -1
  199. package/dist/test/test/engine/cesdk-adapter.test.js +0 -32
  200. package/dist/test/test/engine/cesdk-adapter.test.js.map +0 -1
  201. package/dist/test/test/engine/mutex.test.d.ts +0 -2
  202. package/dist/test/test/engine/mutex.test.d.ts.map +0 -1
  203. package/dist/test/test/engine/mutex.test.js +0 -63
  204. package/dist/test/test/engine/mutex.test.js.map +0 -1
  205. package/dist/test/test/engine/session.test.d.ts +0 -2
  206. package/dist/test/test/engine/session.test.d.ts.map +0 -1
  207. package/dist/test/test/engine/session.test.js +0 -159
  208. package/dist/test/test/engine/session.test.js.map +0 -1
  209. package/dist/test/test/engine/unsplash-source.test.d.ts +0 -2
  210. package/dist/test/test/engine/unsplash-source.test.d.ts.map +0 -1
  211. package/dist/test/test/engine/unsplash-source.test.js +0 -129
  212. package/dist/test/test/engine/unsplash-source.test.js.map +0 -1
  213. package/dist/test/test/engine/uri-resolver.test.d.ts +0 -2
  214. package/dist/test/test/engine/uri-resolver.test.d.ts.map +0 -1
  215. package/dist/test/test/engine/uri-resolver.test.js +0 -38
  216. package/dist/test/test/engine/uri-resolver.test.js.map +0 -1
  217. package/dist/test/test/gateway/hosted-stub.test.d.ts +0 -2
  218. package/dist/test/test/gateway/hosted-stub.test.d.ts.map +0 -1
  219. package/dist/test/test/gateway/hosted-stub.test.js +0 -11
  220. package/dist/test/test/gateway/hosted-stub.test.js.map +0 -1
  221. package/dist/test/test/gateway/local.test.d.ts +0 -2
  222. package/dist/test/test/gateway/local.test.d.ts.map +0 -1
  223. package/dist/test/test/gateway/local.test.js +0 -29
  224. package/dist/test/test/gateway/local.test.js.map +0 -1
  225. package/dist/test/test/handbook-contract.test.d.ts +0 -2
  226. package/dist/test/test/handbook-contract.test.d.ts.map +0 -1
  227. package/dist/test/test/handbook-contract.test.js +0 -79
  228. package/dist/test/test/handbook-contract.test.js.map +0 -1
  229. package/dist/test/test/http/server.test.d.ts +0 -2
  230. package/dist/test/test/http/server.test.d.ts.map +0 -1
  231. package/dist/test/test/http/server.test.js +0 -338
  232. package/dist/test/test/http/server.test.js.map +0 -1
  233. package/dist/test/test/http/session-id-header.test.d.ts +0 -2
  234. package/dist/test/test/http/session-id-header.test.d.ts.map +0 -1
  235. package/dist/test/test/http/session-id-header.test.js +0 -76
  236. package/dist/test/test/http/session-id-header.test.js.map +0 -1
  237. package/dist/test/test/http/session-manager.test.d.ts +0 -2
  238. package/dist/test/test/http/session-manager.test.d.ts.map +0 -1
  239. package/dist/test/test/http/session-manager.test.js +0 -97
  240. package/dist/test/test/http/session-manager.test.js.map +0 -1
  241. package/dist/test/test/http/workspace-auth.test.d.ts +0 -2
  242. package/dist/test/test/http/workspace-auth.test.d.ts.map +0 -1
  243. package/dist/test/test/http/workspace-auth.test.js +0 -40
  244. package/dist/test/test/http/workspace-auth.test.js.map +0 -1
  245. package/dist/test/test/metrics/mount.test.d.ts +0 -2
  246. package/dist/test/test/metrics/mount.test.d.ts.map +0 -1
  247. package/dist/test/test/metrics/mount.test.js +0 -59
  248. package/dist/test/test/metrics/mount.test.js.map +0 -1
  249. package/dist/test/test/metrics/recorder.test.d.ts +0 -2
  250. package/dist/test/test/metrics/recorder.test.d.ts.map +0 -1
  251. package/dist/test/test/metrics/recorder.test.js +0 -118
  252. package/dist/test/test/metrics/recorder.test.js.map +0 -1
  253. package/dist/test/test/revisions/hash.test.d.ts +0 -2
  254. package/dist/test/test/revisions/hash.test.d.ts.map +0 -1
  255. package/dist/test/test/revisions/hash.test.js +0 -33
  256. package/dist/test/test/revisions/hash.test.js.map +0 -1
  257. package/dist/test/test/revisions/store.test.d.ts +0 -2
  258. package/dist/test/test/revisions/store.test.d.ts.map +0 -1
  259. package/dist/test/test/revisions/store.test.js +0 -315
  260. package/dist/test/test/revisions/store.test.js.map +0 -1
  261. package/dist/test/test/server.metrics.test.d.ts +0 -2
  262. package/dist/test/test/server.metrics.test.d.ts.map +0 -1
  263. package/dist/test/test/server.metrics.test.js +0 -87
  264. package/dist/test/test/server.metrics.test.js.map +0 -1
  265. package/dist/test/test/server.test.d.ts +0 -2
  266. package/dist/test/test/server.test.d.ts.map +0 -1
  267. package/dist/test/test/server.test.js +0 -146
  268. package/dist/test/test/server.test.js.map +0 -1
  269. package/dist/test/test/skill-tools.test.d.ts +0 -2
  270. package/dist/test/test/skill-tools.test.d.ts.map +0 -1
  271. package/dist/test/test/skill-tools.test.js +0 -84
  272. package/dist/test/test/skill-tools.test.js.map +0 -1
  273. package/dist/test/test/smoke.test.d.ts +0 -2
  274. package/dist/test/test/smoke.test.d.ts.map +0 -1
  275. package/dist/test/test/smoke.test.js +0 -8
  276. package/dist/test/test/smoke.test.js.map +0 -1
  277. package/dist/test/test/tools/asset-generate.test.d.ts +0 -2
  278. package/dist/test/test/tools/asset-generate.test.d.ts.map +0 -1
  279. package/dist/test/test/tools/asset-generate.test.js +0 -66
  280. package/dist/test/test/tools/asset-generate.test.js.map +0 -1
  281. package/dist/test/test/tools/asset-search.test.d.ts +0 -2
  282. package/dist/test/test/tools/asset-search.test.d.ts.map +0 -1
  283. package/dist/test/test/tools/asset-search.test.js +0 -121
  284. package/dist/test/test/tools/asset-search.test.js.map +0 -1
  285. package/dist/test/test/tools/edit-harness.d.ts +0 -14
  286. package/dist/test/test/tools/edit-harness.d.ts.map +0 -1
  287. package/dist/test/test/tools/edit-harness.js +0 -30
  288. package/dist/test/test/tools/edit-harness.js.map +0 -1
  289. package/dist/test/test/tools/edit.telemetry.test.d.ts +0 -2
  290. package/dist/test/test/tools/edit.telemetry.test.d.ts.map +0 -1
  291. package/dist/test/test/tools/edit.telemetry.test.js +0 -46
  292. package/dist/test/test/tools/edit.telemetry.test.js.map +0 -1
  293. package/dist/test/test/tools/edit.test.d.ts +0 -2
  294. package/dist/test/test/tools/edit.test.d.ts.map +0 -1
  295. package/dist/test/test/tools/edit.test.js +0 -267
  296. package/dist/test/test/tools/edit.test.js.map +0 -1
  297. package/dist/test/test/tools/export-harness.d.ts +0 -13
  298. package/dist/test/test/tools/export-harness.d.ts.map +0 -1
  299. package/dist/test/test/tools/export-harness.js +0 -27
  300. package/dist/test/test/tools/export-harness.js.map +0 -1
  301. package/dist/test/test/tools/export.telemetry.test.d.ts +0 -2
  302. package/dist/test/test/tools/export.telemetry.test.d.ts.map +0 -1
  303. package/dist/test/test/tools/export.telemetry.test.js +0 -65
  304. package/dist/test/test/tools/export.telemetry.test.js.map +0 -1
  305. package/dist/test/test/tools/export.test.d.ts +0 -2
  306. package/dist/test/test/tools/export.test.d.ts.map +0 -1
  307. package/dist/test/test/tools/export.test.js +0 -174
  308. package/dist/test/test/tools/export.test.js.map +0 -1
  309. package/dist/test/test/tools/history.test.d.ts +0 -2
  310. package/dist/test/test/tools/history.test.d.ts.map +0 -1
  311. package/dist/test/test/tools/history.test.js +0 -29
  312. package/dist/test/test/tools/history.test.js.map +0 -1
  313. package/dist/test/test/tools/import.test.d.ts +0 -2
  314. package/dist/test/test/tools/import.test.d.ts.map +0 -1
  315. package/dist/test/test/tools/import.test.js +0 -122
  316. package/dist/test/test/tools/import.test.js.map +0 -1
  317. package/dist/test/test/tools/index.test.d.ts +0 -2
  318. package/dist/test/test/tools/index.test.d.ts.map +0 -1
  319. package/dist/test/test/tools/index.test.js +0 -117
  320. package/dist/test/test/tools/index.test.js.map +0 -1
  321. package/dist/test/test/tools/inspect.test.d.ts +0 -2
  322. package/dist/test/test/tools/inspect.test.d.ts.map +0 -1
  323. package/dist/test/test/tools/inspect.test.js +0 -62
  324. package/dist/test/test/tools/inspect.test.js.map +0 -1
  325. package/dist/test/test/tools/list.test.d.ts +0 -2
  326. package/dist/test/test/tools/list.test.d.ts.map +0 -1
  327. package/dist/test/test/tools/list.test.js +0 -23
  328. package/dist/test/test/tools/list.test.js.map +0 -1
  329. package/dist/test/test/tools/load.integration.test.d.ts +0 -2
  330. package/dist/test/test/tools/load.integration.test.d.ts.map +0 -1
  331. package/dist/test/test/tools/load.integration.test.js +0 -165
  332. package/dist/test/test/tools/load.integration.test.js.map +0 -1
  333. package/dist/test/test/tools/load.test.d.ts +0 -2
  334. package/dist/test/test/tools/load.test.d.ts.map +0 -1
  335. package/dist/test/test/tools/load.test.js +0 -387
  336. package/dist/test/test/tools/load.test.js.map +0 -1
  337. package/dist/test/test/tools/metrics.test.d.ts +0 -2
  338. package/dist/test/test/tools/metrics.test.d.ts.map +0 -1
  339. package/dist/test/test/tools/metrics.test.js +0 -29
  340. package/dist/test/test/tools/metrics.test.js.map +0 -1
  341. package/dist/test/test/tools/open-editor.test.d.ts +0 -2
  342. package/dist/test/test/tools/open-editor.test.d.ts.map +0 -1
  343. package/dist/test/test/tools/open-editor.test.js +0 -53
  344. package/dist/test/test/tools/open-editor.test.js.map +0 -1
  345. package/dist/test/test/tools/preview-harness.d.ts +0 -9
  346. package/dist/test/test/tools/preview-harness.d.ts.map +0 -1
  347. package/dist/test/test/tools/preview-harness.js +0 -23
  348. package/dist/test/test/tools/preview-harness.js.map +0 -1
  349. package/dist/test/test/tools/preview.telemetry.test.d.ts +0 -2
  350. package/dist/test/test/tools/preview.telemetry.test.d.ts.map +0 -1
  351. package/dist/test/test/tools/preview.telemetry.test.js +0 -36
  352. package/dist/test/test/tools/preview.telemetry.test.js.map +0 -1
  353. package/dist/test/test/tools/preview.test.d.ts +0 -2
  354. package/dist/test/test/tools/preview.test.d.ts.map +0 -1
  355. package/dist/test/test/tools/preview.test.js +0 -75
  356. package/dist/test/test/tools/preview.test.js.map +0 -1
  357. package/dist/test/test/tools/rename.test.d.ts +0 -2
  358. package/dist/test/test/tools/rename.test.d.ts.map +0 -1
  359. package/dist/test/test/tools/rename.test.js +0 -16
  360. package/dist/test/test/tools/rename.test.js.map +0 -1
  361. package/dist/test/test/tools/save.test.d.ts +0 -2
  362. package/dist/test/test/tools/save.test.d.ts.map +0 -1
  363. package/dist/test/test/tools/save.test.js +0 -137
  364. package/dist/test/test/tools/save.test.js.map +0 -1
  365. package/dist/test/test/tools/schema-best-practices.d.ts +0 -13
  366. package/dist/test/test/tools/schema-best-practices.d.ts.map +0 -1
  367. package/dist/test/test/tools/schema-best-practices.js +0 -169
  368. package/dist/test/test/tools/schema-best-practices.js.map +0 -1
  369. package/dist/test/test/tools/schema-best-practices.test.d.ts +0 -3
  370. package/dist/test/test/tools/schema-best-practices.test.d.ts.map +0 -1
  371. package/dist/test/test/tools/schema-best-practices.test.js +0 -194
  372. package/dist/test/test/tools/schema-best-practices.test.js.map +0 -1
  373. package/dist/test/test/tools/spans.test.d.ts +0 -2
  374. package/dist/test/test/tools/spans.test.d.ts.map +0 -1
  375. package/dist/test/test/tools/spans.test.js +0 -84
  376. package/dist/test/test/tools/spans.test.js.map +0 -1
  377. package/dist/test/test/viewer/broadcaster.test.d.ts +0 -2
  378. package/dist/test/test/viewer/broadcaster.test.d.ts.map +0 -1
  379. package/dist/test/test/viewer/broadcaster.test.js +0 -53
  380. package/dist/test/test/viewer/broadcaster.test.js.map +0 -1
  381. package/dist/test/test/viewer/cesdk-cdn.test.d.ts +0 -2
  382. package/dist/test/test/viewer/cesdk-cdn.test.d.ts.map +0 -1
  383. package/dist/test/test/viewer/cesdk-cdn.test.js +0 -19
  384. package/dist/test/test/viewer/cesdk-cdn.test.js.map +0 -1
  385. package/dist/test/test/viewer/config-route.test.d.ts +0 -2
  386. package/dist/test/test/viewer/config-route.test.d.ts.map +0 -1
  387. package/dist/test/test/viewer/config-route.test.js +0 -28
  388. package/dist/test/test/viewer/config-route.test.js.map +0 -1
  389. package/dist/test/test/viewer/resource-route.test.d.ts +0 -2
  390. package/dist/test/test/viewer/resource-route.test.d.ts.map +0 -1
  391. package/dist/test/test/viewer/resource-route.test.js +0 -70
  392. package/dist/test/test/viewer/resource-route.test.js.map +0 -1
  393. package/dist/test/test/viewer/router.test.d.ts +0 -2
  394. package/dist/test/test/viewer/router.test.d.ts.map +0 -1
  395. package/dist/test/test/viewer/router.test.js +0 -227
  396. package/dist/test/test/viewer/router.test.js.map +0 -1
  397. package/dist/test/test/viewer/session-mount.test.d.ts +0 -2
  398. package/dist/test/test/viewer/session-mount.test.d.ts.map +0 -1
  399. package/dist/test/test/viewer/session-mount.test.js +0 -52
  400. package/dist/test/test/viewer/session-mount.test.js.map +0 -1
  401. package/dist/test/test/viewer/spa-version.test.d.ts +0 -2
  402. package/dist/test/test/viewer/spa-version.test.d.ts.map +0 -1
  403. package/dist/test/test/viewer/spa-version.test.js +0 -14
  404. package/dist/test/test/viewer/spa-version.test.js.map +0 -1
  405. package/dist/test/test/viewer/url-builder.test.d.ts +0 -2
  406. package/dist/test/test/viewer/url-builder.test.d.ts.map +0 -1
  407. package/dist/test/test/viewer/url-builder.test.js +0 -98
  408. package/dist/test/test/viewer/url-builder.test.js.map +0 -1
  409. package/dist/test/test/workspace/content-hash.test.d.ts +0 -2
  410. package/dist/test/test/workspace/content-hash.test.d.ts.map +0 -1
  411. package/dist/test/test/workspace/content-hash.test.js +0 -17
  412. package/dist/test/test/workspace/content-hash.test.js.map +0 -1
  413. package/dist/test/test/workspace/hosted-stub.test.d.ts +0 -2
  414. package/dist/test/test/workspace/hosted-stub.test.d.ts.map +0 -1
  415. package/dist/test/test/workspace/hosted-stub.test.js +0 -30
  416. package/dist/test/test/workspace/hosted-stub.test.js.map +0 -1
  417. package/dist/test/test/workspace/in-memory.test.d.ts +0 -2
  418. package/dist/test/test/workspace/in-memory.test.d.ts.map +0 -1
  419. package/dist/test/test/workspace/in-memory.test.js +0 -4
  420. package/dist/test/test/workspace/in-memory.test.js.map +0 -1
  421. package/dist/test/test/workspace/node-fs.test.d.ts +0 -2
  422. package/dist/test/test/workspace/node-fs.test.d.ts.map +0 -1
  423. package/dist/test/test/workspace/node-fs.test.js +0 -20
  424. package/dist/test/test/workspace/node-fs.test.js.map +0 -1
  425. package/dist/test/test/workspace/s3.test.d.ts +0 -2
  426. package/dist/test/test/workspace/s3.test.d.ts.map +0 -1
  427. package/dist/test/test/workspace/s3.test.js +0 -153
  428. package/dist/test/test/workspace/s3.test.js.map +0 -1
  429. package/dist/test/test/workspace/scoped.test.d.ts +0 -2
  430. package/dist/test/test/workspace/scoped.test.d.ts.map +0 -1
  431. package/dist/test/test/workspace/scoped.test.js +0 -59
  432. package/dist/test/test/workspace/scoped.test.js.map +0 -1
  433. package/dist/test/test/workspace/shared-contract.d.ts +0 -3
  434. package/dist/test/test/workspace/shared-contract.d.ts.map +0 -1
  435. package/dist/test/test/workspace/shared-contract.js +0 -92
  436. package/dist/test/test/workspace/shared-contract.js.map +0 -1
  437. package/dist/test/tsconfig.test.tsbuildinfo +0 -1
  438. package/dist/test/vitest.config.d.ts +0 -3
  439. package/dist/test/vitest.config.d.ts.map +0 -1
  440. package/dist/test/vitest.config.js +0 -8
  441. package/dist/test/vitest.config.js.map +0 -1
  442. package/dist/tools/metrics.d.ts +0 -11
  443. package/dist/tools/metrics.d.ts.map +0 -1
  444. package/dist/tools/metrics.js +0 -28
  445. package/dist/tools/metrics.js.map +0 -1
  446. package/dist/tools/open-editor.d.ts.map +0 -1
  447. package/dist/tools/open-editor.js.map +0 -1
  448. /package/dist/{dashboard → diagnostics}/spa-assets.generated.d.ts +0 -0
@@ -123,15 +123,16 @@ const MIME_EXT = {
123
123
  function extForMime(mime) {
124
124
  return MIME_EXT[mime] ?? 'bin';
125
125
  }
126
- function concatChunks(chunks) {
127
- const total = chunks.reduce((n, c) => n + c.byteLength, 0);
128
- const out = new Uint8Array(total);
129
- let offset = 0;
130
- for (const c of chunks) {
131
- out.set(c, offset);
132
- offset += c.byteLength;
126
+ /**
127
+ * The buffer URI of a transient resource. @cesdk/node (WASM) exposes it as
128
+ * `.URL`; @cesdk/node-native exposes it as `.uri`.
129
+ */
130
+ function transientResourceUri(resource) {
131
+ const uri = resource.URL ?? resource.uri;
132
+ if (uri === undefined) {
133
+ throw new Error('load: transient resource has neither URL nor uri');
133
134
  }
134
- return out;
135
+ return uri;
135
136
  }
136
137
  /**
137
138
  * Persist one engine resource's bytes into the content-addressed asset store
@@ -139,12 +140,12 @@ function concatChunks(chunks) {
139
140
  * identical path regardless of caller (automatic dedup).
140
141
  */
141
142
  async function persistResourceBytes(eng, fs, uri) {
142
- const chunks = [];
143
- eng.editor.getResourceData(uri, 1 << 20, (chunk) => {
144
- chunks.push(chunk.slice()); // copy the engine may reuse the buffer
145
- return true;
146
- });
147
- const bytes = concatChunks(chunks);
143
+ // getBufferLength + getBufferData is the engine-portable way to read a
144
+ // transient (buffer:) resource's bytes: it exists on both @cesdk/node (WASM)
145
+ // and @cesdk/node-native, whereas getResourceData is WASM-only. slice()
146
+ // copies because the engine may reuse the returned buffer.
147
+ const length = eng.editor.getBufferLength(uri);
148
+ const bytes = eng.editor.getBufferData(uri, 0, length).slice();
148
149
  const sha = await shortHash(bytes);
149
150
  const ext = extForMime(await eng.editor.getMimeType(uri));
150
151
  const relPath = `assets/${sha}.${ext}`;
@@ -156,6 +157,11 @@ async function persistResourceBytes(eng, fs, uri) {
156
157
  /** The engine's error code when a serialized scene still carries a scheme
157
158
  * outside `allowedResourceSchemes` (e.g. a lingering `buffer:`). */
158
159
  const DISALLOWED_SCHEMES_CODE = 'SCENE.DISALLOWED_SCHEMES';
160
+ // `@cesdk/node-native` (as of 1.79.0-nightly.20260708) does not set `err.code`
161
+ // on engine errors, so this predicate never matches on native and the
162
+ // duplicate-transient drain retry below is effectively WASM-only until the
163
+ // upstream fix lands. The first-save path (which normally succeeds after
164
+ // explode) is unaffected.
159
165
  function isDisallowedSchemesError(err) {
160
166
  return (err instanceof Error &&
161
167
  err.code === DISALLOWED_SCHEMES_CODE);
@@ -171,9 +177,10 @@ async function explodeArchiveIntoWorkspace(eng, fs) {
171
177
  // Persist + relocate every transient, remembering each URL's workspace path.
172
178
  const persistedByUrl = new Map();
173
179
  for (const resource of eng.editor.findAllTransientResources()) {
174
- const relPath = await persistResourceBytes(eng, fs, resource.URL);
175
- persistedByUrl.set(resource.URL, relPath);
176
- eng.editor.relocateResource(resource.URL, `workspace://${relPath}`);
180
+ const url = transientResourceUri(resource);
181
+ const relPath = await persistResourceBytes(eng, fs, url);
182
+ persistedByUrl.set(url, relPath);
183
+ eng.editor.relocateResource(url, `workspace://${relPath}`);
177
184
  }
178
185
  try {
179
186
  // Common case: every transient relocated cleanly, so no scheme is disallowed.
@@ -191,7 +198,7 @@ async function explodeArchiveIntoWorkspace(eng, fs) {
191
198
  // the OUTPUT to its already-persisted workspace:// form.
192
199
  //
193
200
  // Two engine constraints shape this (CE.SDK v1.77): (1) the callback MUST
194
- // NOT re-enter the engine — getResourceData/getMimeType from inside it
201
+ // NOT re-enter the engine — getBufferData/getMimeType from inside it
195
202
  // deadlocks saveToString (it holds the engine while awaiting the callback),
196
203
  // so every straggler must be a transient we persisted above and the
197
204
  // callback is a pure lookup; (2) we attach the callback ONLY here, on the
@@ -256,7 +263,7 @@ function classifyUri(uri) {
256
263
  function classifyAssets(eng) {
257
264
  const uris = new Set([
258
265
  ...eng.editor.findAllMediaURIs(),
259
- ...eng.editor.findAllTransientResources().map((r) => r.URL)
266
+ ...eng.editor.findAllTransientResources().map(transientResourceUri)
260
267
  ]);
261
268
  return [...uris].map((uri) => ({ uri, class: classifyUri(uri) }));
262
269
  }
@@ -1 +1 @@
1
- {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/tools/load.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/codesign-mcp/src/tools/load.ts\n//\n// `load` — ingest a native CE.SDK `.scene` file or `.zip` scene archive as a\n// NEW design root revision (the inverse of `save`). Archives are \"exploded\":\n// after `loadFromArchiveURL` their bundled asset bytes live in the engine as\n// ephemeral `buffer:` resources (which scene-level `saveToString` rejects —\n// the CODE-3 bug); we extract each one into the content-addressed asset store\n// (`assets/<sha>.<ext>`), relocate it to a durable `workspace://` URI, and\n// only then serialize. Bare `.scene` files carry no asset bytes: they persist\n// as-is, plus a classification report of every referenced asset URI.\n//\n// Both source variants normalize to bytes up front (fetch for `url`, disk\n// read for `path` — Local only), then dispatch on the zip magic bytes.\n// Archive bytes are staged to a temp file and loaded via a `file://` URL:\n// scene-level CE.SDK v1.77 has no `loadFromArchive(path/bytes)`, only\n// `loadFromArchiveURL`, and `file` is a first-class engine scheme.\n\nimport { promises as nodeFs } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Mutex } from '../engine/mutex.js';\nimport { SCENE_ALLOWED_RESOURCE_SCHEMES } from '../engine/scene-resource-schemes.js';\nimport type { EngineSession } from '../engine/session.js';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport { shortHash } from '../workspace/content-hash.js';\nimport type { WorkspaceFS } from '../workspace/types.js';\n\n/** The slice of CreativeEngine this tool touches — keeps it testable. */\ninterface MinimalLoadEngine {\n scene: {\n loadFromArchiveURL(\n url: string,\n overrideEditorConfig?: boolean,\n waitForResources?: boolean\n ): Promise<number>;\n loadFromString(\n content: string,\n overrideEditorConfig?: boolean,\n waitForResources?: boolean\n ): Promise<number>;\n saveToString(options?: {\n allowedResourceSchemes?: string[];\n onDisallowedResourceScheme?: (\n url: string,\n dataHash: string\n ) => Promise<string>;\n }): Promise<string>;\n };\n editor: {\n findAllTransientResources(): Array<{ URL: string; size: number }>;\n findAllMediaURIs(): string[];\n getResourceData(\n uri: string,\n chunkSize: number,\n onData: (result: Uint8Array) => boolean\n ): void;\n relocateResource(currentUrl: string, relocatedUrl: string): void;\n getMimeType(uri: string): Promise<string>;\n };\n}\n\ntype LoadSource = { url: string } | { path: string };\n\ninterface LoadArgs {\n source: LoadSource;\n title?: string;\n}\n\nconst DESCRIPTION = [\n 'Load a native CE.SDK design — a `.scene` file or a `.zip` scene archive (e.g. a template) — as a NEW design root revision. The inverse of `save`.',\n '',\n 'Source: `{ url }` (http/https) or `{ path }` (absolute local file path; Local only). Archives are ingested: bundled asset bytes are extracted into the workspace as content-addressed `assets/<sha>.<ext>` files, so the loaded design is durable and behaves exactly like a natively-built one.',\n '',\n 'Returns JSON `{ revision, title }` — continue with `edit({ parent: revision, … })`. Bare `.scene` loads additionally return `assets`: a classification of every referenced asset URI by scheme (`workspace` / `external` / `bundle` / `transient` / `other`). External URLs are NOT fetched.'\n].join('\\n');\n\n// The schema is built per-backend, like `save`'s outPath: the `{ path }`\n// source arm is advertised ONLY where local disk paths exist (Local).\n//\n// `source` is a FLAT object with optional `url`/`path` — deliberately NOT a\n// `oneOf` union. A `oneOf` here is the single construct most likely to break\n// tool calling: OpenAI bans it in strict mode, and weaker models (e.g.\n// openrouter:xiaomi/mimo-v2.5-pro, and Claude per samchon/openapi#88) serialize\n// the whole union value as an escaped JSON string, which then fails validation.\n// The \"exactly one of url/path\" invariant is enforced in `call` below, with a\n// clear error message instead of an opaque oneOf mismatch. This mirrors\n// `import`'s `source` shape. See test/tools/schema-best-practices.test.ts.\nfunction buildLoadInputSchema(allowPath: boolean) {\n const sourceProperties: Record<string, unknown> = {\n url: {\n type: 'string' as const,\n description: 'http(s) URL of a `.scene` file or `.zip` scene archive.'\n }\n };\n if (allowPath) {\n sourceProperties.path = {\n type: 'string' as const,\n description:\n 'Absolute local file path of a `.scene` file or `.zip` scene archive. Local-only.'\n };\n }\n return {\n type: 'object' as const,\n properties: {\n source: {\n type: 'object' as const,\n description: allowPath\n ? 'The design to load. Provide exactly one of `url` (http/https) or `path` (absolute local file path; Local only).'\n : 'The design to load. Provide `url` (http/https).',\n properties: sourceProperties,\n additionalProperties: false\n },\n title: {\n type: 'string' as const,\n description:\n \"Display name for the new design. Defaults to the source file's basename.\"\n }\n },\n required: ['source'],\n additionalProperties: false\n };\n}\n\n/** Standard zip local-file-header magic: PK\\x03\\x04. */\nfunction isZip(bytes: Uint8Array): boolean {\n return (\n bytes.length >= 4 &&\n bytes[0] === 0x50 &&\n bytes[1] === 0x4b &&\n bytes[2] === 0x03 &&\n bytes[3] === 0x04\n );\n}\n\n/** Basename of the source, minus a `.zip`/`.scene` extension. */\nfunction deriveTitle(source: LoadSource): string | undefined {\n let base: string;\n if ('url' in source) {\n const raw = new URL(source.url).pathname.split('/').pop() ?? '';\n try {\n base = decodeURIComponent(raw);\n } catch {\n base = raw; // malformed percent sequence — use the basename as-is\n }\n } else {\n // Local paths are literal filenames — never percent-decode them.\n base = source.path.split('/').pop() ?? '';\n }\n const name = base.replace(/\\.(zip|scene)$/i, '');\n return name === '' ? undefined : name;\n}\n\n// Broad enough for what CE.SDK archives actually bundle (images, fonts,\n// audio/video). Unknown types get a neutral `.bin` — the workspace store and\n// the /r/ route are extension-agnostic.\nconst MIME_EXT: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'image/svg+xml': 'svg',\n 'video/mp4': 'mp4',\n 'video/quicktime': 'mov',\n 'audio/mpeg': 'mp3',\n 'audio/wav': 'wav',\n 'audio/x-wav': 'wav',\n 'audio/mp4': 'm4a',\n 'font/ttf': 'ttf',\n 'font/otf': 'otf',\n 'font/woff': 'woff',\n 'font/woff2': 'woff2',\n 'application/x-font-ttf': 'ttf',\n 'application/font-sfnt': 'ttf'\n};\n\nfunction extForMime(mime: string): string {\n return MIME_EXT[mime] ?? 'bin';\n}\n\nfunction concatChunks(chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((n, c) => n + c.byteLength, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const c of chunks) {\n out.set(c, offset);\n offset += c.byteLength;\n }\n return out;\n}\n\n/**\n * Persist one engine resource's bytes into the content-addressed asset store\n * and return its workspace-relative path. Identical bytes land at the\n * identical path regardless of caller (automatic dedup).\n */\nasync function persistResourceBytes(\n eng: MinimalLoadEngine,\n fs: WorkspaceFS,\n uri: string\n): Promise<string> {\n const chunks: Uint8Array[] = [];\n eng.editor.getResourceData(uri, 1 << 20, (chunk) => {\n chunks.push(chunk.slice()); // copy — the engine may reuse the buffer\n return true;\n });\n const bytes = concatChunks(chunks);\n const sha = await shortHash(bytes);\n const ext = extForMime(await eng.editor.getMimeType(uri));\n const relPath = `assets/${sha}.${ext}`;\n if (!(await fs.exists(relPath))) {\n await fs.writeFile(relPath, bytes);\n }\n return relPath;\n}\n\n/** The engine's error code when a serialized scene still carries a scheme\n * outside `allowedResourceSchemes` (e.g. a lingering `buffer:`). */\nconst DISALLOWED_SCHEMES_CODE = 'SCENE.DISALLOWED_SCHEMES';\n\nfunction isDisallowedSchemesError(err: unknown): boolean {\n return (\n err instanceof Error &&\n (err as Error & { code?: string }).code === DISALLOWED_SCHEMES_CODE\n );\n}\n\n/**\n * Extract every transient (`buffer:`) resource into the content-addressed\n * asset store and relocate it to its durable `workspace://` URI, then\n * serialize with the shared scene-scheme allowlist (which permits `workspace`\n * but not `buffer` — see scene-resource-schemes.ts), so a missed transient\n * still fails loudly instead of silently persisting an unloadable scene.\n */\nasync function explodeArchiveIntoWorkspace(\n eng: MinimalLoadEngine,\n fs: WorkspaceFS\n): Promise<string> {\n // Persist + relocate every transient, remembering each URL's workspace path.\n const persistedByUrl = new Map<string, string>();\n for (const resource of eng.editor.findAllTransientResources()) {\n const relPath = await persistResourceBytes(eng, fs, resource.URL);\n persistedByUrl.set(resource.URL, relPath);\n eng.editor.relocateResource(resource.URL, `workspace://${relPath}`);\n }\n try {\n // Common case: every transient relocated cleanly, so no scheme is disallowed.\n return await eng.scene.saveToString({\n allowedResourceSchemes: SCENE_ALLOWED_RESOURCE_SCHEMES\n });\n } catch (err) {\n if (!isDisallowedSchemesError(err)) throw err;\n // A duplicate-content transient survived: relocateResource no-ops when two\n // transients share a content-addressed target (seen with duplicated fonts\n // in premium-template archives), so the engine still serializes that\n // straggler's buffer: URL. Retry with a drain callback that rewrites it in\n // the OUTPUT to its already-persisted workspace:// form.\n //\n // Two engine constraints shape this (CE.SDK v1.77): (1) the callback MUST\n // NOT re-enter the engine — getResourceData/getMimeType from inside it\n // deadlocks saveToString (it holds the engine while awaiting the callback),\n // so every straggler must be a transient we persisted above and the\n // callback is a pure lookup; (2) we attach the callback ONLY here, on the\n // failure path — passing onDisallowedResourceScheme when nothing is\n // disallowed hangs saveToString.\n const scene = await eng.scene.saveToString({\n allowedResourceSchemes: SCENE_ALLOWED_RESOURCE_SCHEMES,\n onDisallowedResourceScheme: async (url) => {\n const relPath = persistedByUrl.get(url);\n if (relPath === undefined) {\n // A buffer: URL we never enumerated as a transient — should not\n // happen; fail loudly rather than persist an unloadable scene.\n throw new Error(\n `load: cannot drain transient \"${url}\" — not persisted during explode.`\n );\n }\n return `workspace://${relPath}`;\n }\n });\n // The drain fixed the serialized OUTPUT, but the engine still holds the\n // un-relocatable straggler in memory — its live state now diverges from the\n // persisted scene. `load` commits this slot warm for the next edit, so\n // reload the drained scene to realign: reloading collapses the duplicate\n // typeface onto its single workspace:// URI, leaving no buffer: straggler.\n // Without this, the next edit reuses the warm slot and its plain\n // saveToString (no drain) fails on the lingering buffer: URL.\n await eng.scene.loadFromString(scene, false, true);\n return scene;\n }\n}\n\ntype AssetClass = 'workspace' | 'external' | 'bundle' | 'transient' | 'other';\n\ninterface AssetReportEntry {\n uri: string;\n class: AssetClass;\n}\n\n/**\n * Classify one asset URI by its scheme. Only a `buffer:` URI is genuinely\n * `transient` (in-memory, silently lost on the next save); every scheme in\n * SCENE_ALLOWED_RESOURCE_SCHEMES (workspace/http/https/bundle/file/…) is durable.\n */\nfunction classifyUri(uri: string): AssetClass {\n if (uri.startsWith('workspace://')) return 'workspace';\n if (uri.startsWith('http://') || uri.startsWith('https://'))\n return 'external';\n if (uri.startsWith('bundle://')) return 'bundle';\n if (uri.startsWith('buffer:')) return 'transient';\n return 'other';\n}\n\n/**\n * Classify every asset reference of a bare `.scene` (which carries no asset\n * bytes) by scheme. A referenced asset does NOT reliably surface in\n * `findAllMediaURIs()` alone: durable re-fetchable refs the engine keeps as\n * URLs (e.g. http(s)) come back there, while anything it had to load into\n * memory — `workspace://` (resolved via our URI resolver), `bundle://`\n * fallbacks, and genuine `buffer:` orphans — comes back from\n * `findAllTransientResources()` carrying its real URI. So we read BOTH lists\n * and classify by scheme, deduped by URI.\n *\n * Verified against the real engine (CODE-3): after a waitForResources load a\n * resolved `workspace://` asset appears ONLY as a transient resource (never in\n * findAllMediaURIs), and an UNRESOLVABLE `workspace://` ref is silently dropped\n * and substituted (e.g. a bundled fallback font) — so there is no reliable\n * \"dangling\" signal to report, and no such class exists. External URLs are\n * reported, never fetched.\n */\nfunction classifyAssets(eng: MinimalLoadEngine): AssetReportEntry[] {\n const uris = new Set<string>([\n ...eng.editor.findAllMediaURIs(),\n ...eng.editor.findAllTransientResources().map((r) => r.URL)\n ]);\n return [...uris].map((uri) => ({ uri, class: classifyUri(uri) }));\n}\n\nfunction errorResult(text: string): {\n isError: true;\n content: Array<{ type: 'text'; text: string }>;\n} {\n return { isError: true, content: [{ type: 'text' as const, text }] };\n}\n\nexport interface CreateLoadToolOptions<E = unknown> {\n fs: WorkspaceFS;\n store: RevisionStore;\n session: EngineSession<E>;\n /** Guards the shared engine — load-then-explode must not interleave with another engine-touching call. */\n engineLock: Mutex;\n /** Whether to honour a `{ path }` source (Local: yes; Hosted/sandbox: no). */\n allowPath?: boolean;\n /** Called after the new root revision is written (viewer SSE hook). */\n onLeafWritten?: (event: {\n revision: string;\n rootRevision: string;\n }) => void | Promise<void>;\n /** Render a thumbnail for the new revision. Best-effort: failures are swallowed. */\n renderThumbnail?: (event: {\n revision: string;\n engine: E;\n }) => Promise<Uint8Array>;\n}\n\nexport function createLoadTool<E = unknown>(\n opts: CreateLoadToolOptions<E>\n): CodesignMcpToolRegistration {\n const {\n fs,\n store,\n session,\n engineLock,\n allowPath = false,\n onLeafWritten,\n renderThumbnail\n } = opts;\n return {\n name: 'load',\n description: DESCRIPTION,\n inputSchema: buildLoadInputSchema(allowPath),\n call: async (rawArgs) => {\n const args = (rawArgs ?? {}) as Partial<LoadArgs>;\n const source = args.source;\n if (\n source === undefined ||\n typeof source !== 'object' ||\n 'url' in source === 'path' in source\n ) {\n return errorResult(\n 'load: `source` is required — exactly one of `{ url }` or `{ path }`.'\n );\n }\n if ('path' in source && !allowPath) {\n return errorResult(\n 'load: `source.path` is not supported on this (hosted) backend. Pass a `{ url }` source instead.'\n );\n }\n // Ingest bytes OUTSIDE the engine lock — network/disk I/O must not\n // block concurrent engine work.\n let bytes: Uint8Array;\n if ('url' in source) {\n let parsed: URL;\n try {\n parsed = new URL(source.url);\n } catch {\n return errorResult(`load: invalid URL \"${source.url}\".`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return errorResult('load: `source.url` must be http(s).');\n }\n let resp: Response;\n try {\n resp = await fetch(source.url);\n } catch (err) {\n return errorResult(\n `load: fetching \"${source.url}\" failed — ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n if (!resp.ok) {\n return errorResult(\n `load: fetching \"${source.url}\" failed with HTTP ${resp.status}.`\n );\n }\n try {\n bytes = new Uint8Array(await resp.arrayBuffer());\n } catch (err) {\n return errorResult(\n `load: reading the response body of \"${source.url}\" failed — ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n } else {\n try {\n bytes = new Uint8Array(await nodeFs.readFile(source.path));\n } catch (err) {\n return errorResult(\n `load: reading \"${source.path}\" failed — ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n }\n const title = args.title ?? deriveTitle(source);\n // The engine is loaded and mutated across several awaits — serialize\n // against any other engine-touching tool call.\n return engineLock.runExclusive(async () => {\n const slot = await session.acquire(null);\n const eng = slot.engine as MinimalLoadEngine;\n try {\n let scene: string;\n let assets: AssetReportEntry[] | undefined;\n if (isZip(bytes)) {\n // Stage to a temp file and load via file:// — scene-level CE.SDK\n // has no loadFromArchive(bytes/path), only loadFromArchiveURL.\n const stagedDir = await nodeFs.mkdtemp(\n join(tmpdir(), 'codesign-load-')\n );\n const staged = join(stagedDir, 'archive.zip');\n try {\n await nodeFs.writeFile(staged, bytes);\n // waitForResources: the archive's bundled bytes must be fully\n // materialized as buffer: resources before we enumerate them.\n await eng.scene.loadFromArchiveURL(\n pathToFileURL(staged).href,\n false,\n true\n );\n } finally {\n await nodeFs\n .rm(stagedDir, { recursive: true, force: true })\n .catch(() => {});\n }\n scene = await explodeArchiveIntoWorkspace(eng, fs);\n } else {\n const content = new TextDecoder().decode(bytes).trim();\n await eng.scene.loadFromString(content, false, true);\n assets = classifyAssets(eng);\n scene = content;\n }\n const written = await store.write({ scene, parent: null, title });\n session.commit(slot, written.id);\n // Thumbnail block mirrors edit.ts — duplication is deliberate (KISS).\n if (renderThumbnail) {\n try {\n const thumb = await renderThumbnail({\n revision: written.id,\n engine: slot.engine\n });\n const shard = written.id.slice(0, 2);\n const rest = written.id.slice(2);\n await fs.writeFile(\n `exports/${shard}/${rest}/preview.webp`,\n thumb\n );\n } catch {\n // best-effort; thumbnail failure must not poison the load\n }\n }\n if (onLeafWritten) {\n // A new root is its own lineage root.\n await onLeafWritten({\n revision: written.id,\n rootRevision: written.id\n });\n }\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n revision: written.id,\n title: await store.currentTitle(written.id),\n ...(assets !== undefined ? { assets } : {})\n })\n }\n ]\n };\n } catch (err) {\n session.markDirty(slot);\n return errorResult(\n `load: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n }\n });\n }\n };\n}\n"]}
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/tools/load.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/codesign-mcp/src/tools/load.ts\n//\n// `load` — ingest a native CE.SDK `.scene` file or `.zip` scene archive as a\n// NEW design root revision (the inverse of `save`). Archives are \"exploded\":\n// after `loadFromArchiveURL` their bundled asset bytes live in the engine as\n// ephemeral `buffer:` resources (which scene-level `saveToString` rejects —\n// the CODE-3 bug); we extract each one into the content-addressed asset store\n// (`assets/<sha>.<ext>`), relocate it to a durable `workspace://` URI, and\n// only then serialize. Bare `.scene` files carry no asset bytes: they persist\n// as-is, plus a classification report of every referenced asset URI.\n//\n// Both source variants normalize to bytes up front (fetch for `url`, disk\n// read for `path` — Local only), then dispatch on the zip magic bytes.\n// Archive bytes are staged to a temp file and loaded via a `file://` URL:\n// scene-level CE.SDK v1.77 has no `loadFromArchive(path/bytes)`, only\n// `loadFromArchiveURL`, and `file` is a first-class engine scheme.\n\nimport { promises as nodeFs } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Mutex } from '../engine/mutex.js';\nimport { SCENE_ALLOWED_RESOURCE_SCHEMES } from '../engine/scene-resource-schemes.js';\nimport type { EngineSession } from '../engine/session.js';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport { shortHash } from '../workspace/content-hash.js';\nimport type { WorkspaceFS } from '../workspace/types.js';\n\n/** The slice of CreativeEngine this tool touches — keeps it testable. */\ninterface MinimalLoadEngine {\n scene: {\n loadFromArchiveURL(\n url: string,\n overrideEditorConfig?: boolean,\n waitForResources?: boolean\n ): Promise<number>;\n loadFromString(\n content: string,\n overrideEditorConfig?: boolean,\n waitForResources?: boolean\n ): Promise<number>;\n saveToString(options?: {\n allowedResourceSchemes?: string[];\n onDisallowedResourceScheme?: (\n url: string,\n dataHash: string\n ) => Promise<string>;\n }): Promise<string>;\n };\n editor: {\n findAllTransientResources(): Array<{\n URL?: string;\n uri?: string;\n size: number;\n }>;\n findAllMediaURIs(): string[];\n getBufferLength(uri: string): number;\n getBufferData(uri: string, offset: number, length: number): Uint8Array;\n relocateResource(currentUrl: string, relocatedUrl: string): void;\n getMimeType(uri: string): Promise<string>;\n };\n}\n\ntype LoadSource = { url: string } | { path: string };\n\ninterface LoadArgs {\n source: LoadSource;\n title?: string;\n}\n\nconst DESCRIPTION = [\n 'Load a native CE.SDK design — a `.scene` file or a `.zip` scene archive (e.g. a template) — as a NEW design root revision. The inverse of `save`.',\n '',\n 'Source: `{ url }` (http/https) or `{ path }` (absolute local file path; Local only). Archives are ingested: bundled asset bytes are extracted into the workspace as content-addressed `assets/<sha>.<ext>` files, so the loaded design is durable and behaves exactly like a natively-built one.',\n '',\n 'Returns JSON `{ revision, title }` — continue with `edit({ parent: revision, … })`. Bare `.scene` loads additionally return `assets`: a classification of every referenced asset URI by scheme (`workspace` / `external` / `bundle` / `transient` / `other`). External URLs are NOT fetched.'\n].join('\\n');\n\n// The schema is built per-backend, like `save`'s outPath: the `{ path }`\n// source arm is advertised ONLY where local disk paths exist (Local).\n//\n// `source` is a FLAT object with optional `url`/`path` — deliberately NOT a\n// `oneOf` union. A `oneOf` here is the single construct most likely to break\n// tool calling: OpenAI bans it in strict mode, and weaker models (e.g.\n// openrouter:xiaomi/mimo-v2.5-pro, and Claude per samchon/openapi#88) serialize\n// the whole union value as an escaped JSON string, which then fails validation.\n// The \"exactly one of url/path\" invariant is enforced in `call` below, with a\n// clear error message instead of an opaque oneOf mismatch. This mirrors\n// `import`'s `source` shape. See test/tools/schema-best-practices.test.ts.\nfunction buildLoadInputSchema(allowPath: boolean) {\n const sourceProperties: Record<string, unknown> = {\n url: {\n type: 'string' as const,\n description: 'http(s) URL of a `.scene` file or `.zip` scene archive.'\n }\n };\n if (allowPath) {\n sourceProperties.path = {\n type: 'string' as const,\n description:\n 'Absolute local file path of a `.scene` file or `.zip` scene archive. Local-only.'\n };\n }\n return {\n type: 'object' as const,\n properties: {\n source: {\n type: 'object' as const,\n description: allowPath\n ? 'The design to load. Provide exactly one of `url` (http/https) or `path` (absolute local file path; Local only).'\n : 'The design to load. Provide `url` (http/https).',\n properties: sourceProperties,\n additionalProperties: false\n },\n title: {\n type: 'string' as const,\n description:\n \"Display name for the new design. Defaults to the source file's basename.\"\n }\n },\n required: ['source'],\n additionalProperties: false\n };\n}\n\n/** Standard zip local-file-header magic: PK\\x03\\x04. */\nfunction isZip(bytes: Uint8Array): boolean {\n return (\n bytes.length >= 4 &&\n bytes[0] === 0x50 &&\n bytes[1] === 0x4b &&\n bytes[2] === 0x03 &&\n bytes[3] === 0x04\n );\n}\n\n/** Basename of the source, minus a `.zip`/`.scene` extension. */\nfunction deriveTitle(source: LoadSource): string | undefined {\n let base: string;\n if ('url' in source) {\n const raw = new URL(source.url).pathname.split('/').pop() ?? '';\n try {\n base = decodeURIComponent(raw);\n } catch {\n base = raw; // malformed percent sequence — use the basename as-is\n }\n } else {\n // Local paths are literal filenames — never percent-decode them.\n base = source.path.split('/').pop() ?? '';\n }\n const name = base.replace(/\\.(zip|scene)$/i, '');\n return name === '' ? undefined : name;\n}\n\n// Broad enough for what CE.SDK archives actually bundle (images, fonts,\n// audio/video). Unknown types get a neutral `.bin` — the workspace store and\n// the /r/ route are extension-agnostic.\nconst MIME_EXT: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'image/svg+xml': 'svg',\n 'video/mp4': 'mp4',\n 'video/quicktime': 'mov',\n 'audio/mpeg': 'mp3',\n 'audio/wav': 'wav',\n 'audio/x-wav': 'wav',\n 'audio/mp4': 'm4a',\n 'font/ttf': 'ttf',\n 'font/otf': 'otf',\n 'font/woff': 'woff',\n 'font/woff2': 'woff2',\n 'application/x-font-ttf': 'ttf',\n 'application/font-sfnt': 'ttf'\n};\n\nfunction extForMime(mime: string): string {\n return MIME_EXT[mime] ?? 'bin';\n}\n\n/**\n * The buffer URI of a transient resource. @cesdk/node (WASM) exposes it as\n * `.URL`; @cesdk/node-native exposes it as `.uri`.\n */\nfunction transientResourceUri(resource: {\n URL?: string;\n uri?: string;\n}): string {\n const uri = resource.URL ?? resource.uri;\n if (uri === undefined) {\n throw new Error('load: transient resource has neither URL nor uri');\n }\n return uri;\n}\n\n/**\n * Persist one engine resource's bytes into the content-addressed asset store\n * and return its workspace-relative path. Identical bytes land at the\n * identical path regardless of caller (automatic dedup).\n */\nasync function persistResourceBytes(\n eng: MinimalLoadEngine,\n fs: WorkspaceFS,\n uri: string\n): Promise<string> {\n // getBufferLength + getBufferData is the engine-portable way to read a\n // transient (buffer:) resource's bytes: it exists on both @cesdk/node (WASM)\n // and @cesdk/node-native, whereas getResourceData is WASM-only. slice()\n // copies because the engine may reuse the returned buffer.\n const length = eng.editor.getBufferLength(uri);\n const bytes = eng.editor.getBufferData(uri, 0, length).slice();\n const sha = await shortHash(bytes);\n const ext = extForMime(await eng.editor.getMimeType(uri));\n const relPath = `assets/${sha}.${ext}`;\n if (!(await fs.exists(relPath))) {\n await fs.writeFile(relPath, bytes);\n }\n return relPath;\n}\n\n/** The engine's error code when a serialized scene still carries a scheme\n * outside `allowedResourceSchemes` (e.g. a lingering `buffer:`). */\nconst DISALLOWED_SCHEMES_CODE = 'SCENE.DISALLOWED_SCHEMES';\n\n// `@cesdk/node-native` (as of 1.79.0-nightly.20260708) does not set `err.code`\n// on engine errors, so this predicate never matches on native and the\n// duplicate-transient drain retry below is effectively WASM-only until the\n// upstream fix lands. The first-save path (which normally succeeds after\n// explode) is unaffected.\nfunction isDisallowedSchemesError(err: unknown): boolean {\n return (\n err instanceof Error &&\n (err as Error & { code?: string }).code === DISALLOWED_SCHEMES_CODE\n );\n}\n\n/**\n * Extract every transient (`buffer:`) resource into the content-addressed\n * asset store and relocate it to its durable `workspace://` URI, then\n * serialize with the shared scene-scheme allowlist (which permits `workspace`\n * but not `buffer` — see scene-resource-schemes.ts), so a missed transient\n * still fails loudly instead of silently persisting an unloadable scene.\n */\nasync function explodeArchiveIntoWorkspace(\n eng: MinimalLoadEngine,\n fs: WorkspaceFS\n): Promise<string> {\n // Persist + relocate every transient, remembering each URL's workspace path.\n const persistedByUrl = new Map<string, string>();\n for (const resource of eng.editor.findAllTransientResources()) {\n const url = transientResourceUri(resource);\n const relPath = await persistResourceBytes(eng, fs, url);\n persistedByUrl.set(url, relPath);\n eng.editor.relocateResource(url, `workspace://${relPath}`);\n }\n try {\n // Common case: every transient relocated cleanly, so no scheme is disallowed.\n return await eng.scene.saveToString({\n allowedResourceSchemes: SCENE_ALLOWED_RESOURCE_SCHEMES\n });\n } catch (err) {\n if (!isDisallowedSchemesError(err)) throw err;\n // A duplicate-content transient survived: relocateResource no-ops when two\n // transients share a content-addressed target (seen with duplicated fonts\n // in premium-template archives), so the engine still serializes that\n // straggler's buffer: URL. Retry with a drain callback that rewrites it in\n // the OUTPUT to its already-persisted workspace:// form.\n //\n // Two engine constraints shape this (CE.SDK v1.77): (1) the callback MUST\n // NOT re-enter the engine — getBufferData/getMimeType from inside it\n // deadlocks saveToString (it holds the engine while awaiting the callback),\n // so every straggler must be a transient we persisted above and the\n // callback is a pure lookup; (2) we attach the callback ONLY here, on the\n // failure path — passing onDisallowedResourceScheme when nothing is\n // disallowed hangs saveToString.\n const scene = await eng.scene.saveToString({\n allowedResourceSchemes: SCENE_ALLOWED_RESOURCE_SCHEMES,\n onDisallowedResourceScheme: async (url) => {\n const relPath = persistedByUrl.get(url);\n if (relPath === undefined) {\n // A buffer: URL we never enumerated as a transient — should not\n // happen; fail loudly rather than persist an unloadable scene.\n throw new Error(\n `load: cannot drain transient \"${url}\" — not persisted during explode.`\n );\n }\n return `workspace://${relPath}`;\n }\n });\n // The drain fixed the serialized OUTPUT, but the engine still holds the\n // un-relocatable straggler in memory — its live state now diverges from the\n // persisted scene. `load` commits this slot warm for the next edit, so\n // reload the drained scene to realign: reloading collapses the duplicate\n // typeface onto its single workspace:// URI, leaving no buffer: straggler.\n // Without this, the next edit reuses the warm slot and its plain\n // saveToString (no drain) fails on the lingering buffer: URL.\n await eng.scene.loadFromString(scene, false, true);\n return scene;\n }\n}\n\ntype AssetClass = 'workspace' | 'external' | 'bundle' | 'transient' | 'other';\n\ninterface AssetReportEntry {\n uri: string;\n class: AssetClass;\n}\n\n/**\n * Classify one asset URI by its scheme. Only a `buffer:` URI is genuinely\n * `transient` (in-memory, silently lost on the next save); every scheme in\n * SCENE_ALLOWED_RESOURCE_SCHEMES (workspace/http/https/bundle/file/…) is durable.\n */\nfunction classifyUri(uri: string): AssetClass {\n if (uri.startsWith('workspace://')) return 'workspace';\n if (uri.startsWith('http://') || uri.startsWith('https://'))\n return 'external';\n if (uri.startsWith('bundle://')) return 'bundle';\n if (uri.startsWith('buffer:')) return 'transient';\n return 'other';\n}\n\n/**\n * Classify every asset reference of a bare `.scene` (which carries no asset\n * bytes) by scheme. A referenced asset does NOT reliably surface in\n * `findAllMediaURIs()` alone: durable re-fetchable refs the engine keeps as\n * URLs (e.g. http(s)) come back there, while anything it had to load into\n * memory — `workspace://` (resolved via our URI resolver), `bundle://`\n * fallbacks, and genuine `buffer:` orphans — comes back from\n * `findAllTransientResources()` carrying its real URI. So we read BOTH lists\n * and classify by scheme, deduped by URI.\n *\n * Verified against the real engine (CODE-3): after a waitForResources load a\n * resolved `workspace://` asset appears ONLY as a transient resource (never in\n * findAllMediaURIs), and an UNRESOLVABLE `workspace://` ref is silently dropped\n * and substituted (e.g. a bundled fallback font) — so there is no reliable\n * \"dangling\" signal to report, and no such class exists. External URLs are\n * reported, never fetched.\n */\nfunction classifyAssets(eng: MinimalLoadEngine): AssetReportEntry[] {\n const uris = new Set<string>([\n ...eng.editor.findAllMediaURIs(),\n ...eng.editor.findAllTransientResources().map(transientResourceUri)\n ]);\n return [...uris].map((uri) => ({ uri, class: classifyUri(uri) }));\n}\n\nfunction errorResult(text: string): {\n isError: true;\n content: Array<{ type: 'text'; text: string }>;\n} {\n return { isError: true, content: [{ type: 'text' as const, text }] };\n}\n\nexport interface CreateLoadToolOptions<E = unknown> {\n fs: WorkspaceFS;\n store: RevisionStore;\n session: EngineSession<E>;\n /** Guards the shared engine — load-then-explode must not interleave with another engine-touching call. */\n engineLock: Mutex;\n /** Whether to honour a `{ path }` source (Local: yes; Hosted/sandbox: no). */\n allowPath?: boolean;\n /** Called after the new root revision is written (viewer SSE hook). */\n onLeafWritten?: (event: {\n revision: string;\n rootRevision: string;\n }) => void | Promise<void>;\n /** Render a thumbnail for the new revision. Best-effort: failures are swallowed. */\n renderThumbnail?: (event: {\n revision: string;\n engine: E;\n }) => Promise<Uint8Array>;\n}\n\nexport function createLoadTool<E = unknown>(\n opts: CreateLoadToolOptions<E>\n): CodesignMcpToolRegistration {\n const {\n fs,\n store,\n session,\n engineLock,\n allowPath = false,\n onLeafWritten,\n renderThumbnail\n } = opts;\n return {\n name: 'load',\n description: DESCRIPTION,\n inputSchema: buildLoadInputSchema(allowPath),\n call: async (rawArgs) => {\n const args = (rawArgs ?? {}) as Partial<LoadArgs>;\n const source = args.source;\n if (\n source === undefined ||\n typeof source !== 'object' ||\n 'url' in source === 'path' in source\n ) {\n return errorResult(\n 'load: `source` is required — exactly one of `{ url }` or `{ path }`.'\n );\n }\n if ('path' in source && !allowPath) {\n return errorResult(\n 'load: `source.path` is not supported on this (hosted) backend. Pass a `{ url }` source instead.'\n );\n }\n // Ingest bytes OUTSIDE the engine lock — network/disk I/O must not\n // block concurrent engine work.\n let bytes: Uint8Array;\n if ('url' in source) {\n let parsed: URL;\n try {\n parsed = new URL(source.url);\n } catch {\n return errorResult(`load: invalid URL \"${source.url}\".`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return errorResult('load: `source.url` must be http(s).');\n }\n let resp: Response;\n try {\n resp = await fetch(source.url);\n } catch (err) {\n return errorResult(\n `load: fetching \"${source.url}\" failed — ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n if (!resp.ok) {\n return errorResult(\n `load: fetching \"${source.url}\" failed with HTTP ${resp.status}.`\n );\n }\n try {\n bytes = new Uint8Array(await resp.arrayBuffer());\n } catch (err) {\n return errorResult(\n `load: reading the response body of \"${source.url}\" failed — ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n } else {\n try {\n bytes = new Uint8Array(await nodeFs.readFile(source.path));\n } catch (err) {\n return errorResult(\n `load: reading \"${source.path}\" failed — ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n }\n const title = args.title ?? deriveTitle(source);\n // The engine is loaded and mutated across several awaits — serialize\n // against any other engine-touching tool call.\n return engineLock.runExclusive(async () => {\n const slot = await session.acquire(null);\n const eng = slot.engine as MinimalLoadEngine;\n try {\n let scene: string;\n let assets: AssetReportEntry[] | undefined;\n if (isZip(bytes)) {\n // Stage to a temp file and load via file:// — scene-level CE.SDK\n // has no loadFromArchive(bytes/path), only loadFromArchiveURL.\n const stagedDir = await nodeFs.mkdtemp(\n join(tmpdir(), 'codesign-load-')\n );\n const staged = join(stagedDir, 'archive.zip');\n try {\n await nodeFs.writeFile(staged, bytes);\n // waitForResources: the archive's bundled bytes must be fully\n // materialized as buffer: resources before we enumerate them.\n await eng.scene.loadFromArchiveURL(\n pathToFileURL(staged).href,\n false,\n true\n );\n } finally {\n await nodeFs\n .rm(stagedDir, { recursive: true, force: true })\n .catch(() => {});\n }\n scene = await explodeArchiveIntoWorkspace(eng, fs);\n } else {\n const content = new TextDecoder().decode(bytes).trim();\n await eng.scene.loadFromString(content, false, true);\n assets = classifyAssets(eng);\n scene = content;\n }\n const written = await store.write({ scene, parent: null, title });\n session.commit(slot, written.id);\n // Thumbnail block mirrors edit.ts — duplication is deliberate (KISS).\n if (renderThumbnail) {\n try {\n const thumb = await renderThumbnail({\n revision: written.id,\n engine: slot.engine\n });\n const shard = written.id.slice(0, 2);\n const rest = written.id.slice(2);\n await fs.writeFile(\n `exports/${shard}/${rest}/preview.webp`,\n thumb\n );\n } catch {\n // best-effort; thumbnail failure must not poison the load\n }\n }\n if (onLeafWritten) {\n // A new root is its own lineage root.\n await onLeafWritten({\n revision: written.id,\n rootRevision: written.id\n });\n }\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n revision: written.id,\n title: await store.currentTitle(written.id),\n ...(assets !== undefined ? { assets } : {})\n })\n }\n ]\n };\n } catch (err) {\n session.markDirty(slot);\n return errorResult(\n `load: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n }\n });\n }\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/tools/preview.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AA6CpB,MAAM,WAAW,wBAAwB,CAAC,CAAC,GAAG,OAAO;IACnD,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,yHAAyH;IACzH,UAAU,EAAE,KAAK,CAAC;IAClB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAUD,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAC3C,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAChC,2BAA2B,CAsG7B"}
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/tools/preview.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AA8CpB,MAAM,WAAW,wBAAwB,CAAC,CAAC,GAAG,OAAO;IACnD,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,yHAAyH;IACzH,UAAU,EAAE,KAAK,CAAC;IAClB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAUD,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAC3C,IAAI,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAChC,2BAA2B,CA4H7B"}
@@ -1,9 +1,9 @@
1
1
  // packages/codesign-mcp/src/tools/preview.ts
2
2
  //
3
- // `preview` — renders a block to an inline image. Adds an optional `revision`
4
- // (spec §6.1): when supplied and the engine slot is at a different revision,
5
- // the session loads that revision before exporting. Without `revision`, renders
6
- // the engine's current state (uses `session.currentSlot()` side-effect-free).
3
+ // `preview` — renders a block to an inline image. `revision` is REQUIRED
4
+ // (CODE-76): the session loads that revision before exporting, so `blockId`
5
+ // always resolves against the scene it belongs to never against a different
6
+ // design a concurrent agent may have loaded into the shared engine.
7
7
  import { performance } from 'node:perf_hooks';
8
8
  import { SpanRecorder, NOOP_TELEMETRY_SINK, summarizeResult, toErrorShape } from './spans.js';
9
9
  const MAX_DIMENSION_PX = 1800;
@@ -11,9 +11,9 @@ const DEFAULT_TARGET_WIDTH = 512;
11
11
  const DESCRIPTION = [
12
12
  'Render a block (almost always a page) to an inline image so you can see the result.',
13
13
  '',
14
- "`blockId` is REQUIRED preview the page you're working on, not the scene root.",
14
+ "`blockId` and `revision` are BOTH REQUIRED. Preview the page you're working on (not the scene root), and pass the `revision` the block belongs to — the id `edit` returned.",
15
15
  '',
16
- "Optional `revision`: if supplied, loads that revision into the engine before rendering. Without it, renders the engine's current state (almost always what you want directly after `edit`).",
16
+ 'The given `revision` is loaded before rendering, so `blockId` always resolves against the right scene never against a different design another agent may have loaded on a shared server.',
17
17
  '',
18
18
  "Defaults: format='webp', width=512. Width/height clamped at 1800 px (Anthropic many-image limit)."
19
19
  ].join('\n');
@@ -23,13 +23,13 @@ const PREVIEW_INPUT_SCHEMA = {
23
23
  blockId: { type: 'integer' },
24
24
  revision: {
25
25
  type: 'string',
26
- description: "Revision id; defaults to the engine's current state."
26
+ description: 'Revision id the block belongs to (from `edit`). Loaded before rendering so `blockId` resolves against the right scene.'
27
27
  },
28
28
  format: { enum: ['webp', 'png', 'jpeg'] },
29
29
  width: { type: 'integer', minimum: 1 },
30
30
  height: { type: 'integer', minimum: 1 }
31
31
  },
32
- required: ['blockId'],
32
+ required: ['blockId', 'revision'],
33
33
  additionalProperties: false
34
34
  };
35
35
  function bytesToBase64(bytes) {
@@ -53,6 +53,7 @@ export function createPreviewTool(opts) {
53
53
  let result;
54
54
  let threw;
55
55
  let revision;
56
+ let exportError;
56
57
  try {
57
58
  result = await (async () => {
58
59
  const args = (rawArgs ?? {});
@@ -67,38 +68,30 @@ export function createPreviewTool(opts) {
67
68
  ]
68
69
  };
69
70
  }
70
- // Load (if a revision was given) and export read the shared engine across
71
- // awaits — serialize against any concurrent engine-touching tool call.
71
+ if (typeof args.revision !== 'string') {
72
+ return {
73
+ isError: true,
74
+ content: [
75
+ {
76
+ type: 'text',
77
+ text: 'preview: `revision` (string) is required — pass the revision the block belongs to (the id `edit` returned). It is loaded before rendering so `blockId` always resolves against the right scene.'
78
+ }
79
+ ]
80
+ };
81
+ }
82
+ const revId = args.revision;
83
+ revision = revId;
84
+ // Load the requested revision, then export — both read the shared engine
85
+ // across awaits, so serialize against any concurrent engine-touching tool
86
+ // call (see engine/mutex.ts).
72
87
  const beforeLock = performance.now();
73
88
  return engineLock.runExclusive(async () => {
74
89
  rec.add('lock_wait', performance.now() - beforeLock);
75
- let slot;
76
- if (args.revision !== undefined) {
77
- const revId = args.revision;
78
- revision = revId;
79
- const rev = await rec.time('workspace_read', () => store.read(revId));
80
- slot = await rec.time('engine_scene_load', async () => {
81
- await session.populate(revId, rev.scene);
82
- return session.acquire(revId);
83
- });
84
- }
85
- else {
86
- // No revision: render the current engine state via the side-effect-free
87
- // currentSlot accessor (does NOT reset). If no slot is warm, error.
88
- const current = session.currentSlot();
89
- if (!current) {
90
- return {
91
- isError: true,
92
- content: [
93
- {
94
- type: 'text',
95
- text: 'preview: no engine state to render. Run `edit` first, or pass `revision` to load a specific revision.'
96
- }
97
- ]
98
- };
99
- }
100
- slot = current;
101
- }
90
+ const rev = await rec.time('workspace_read', () => store.read(revId));
91
+ const slot = await rec.time('engine_scene_load', async () => {
92
+ await session.populate(revId, rev.scene);
93
+ return session.acquire(revId);
94
+ });
102
95
  const eng = slot.engine;
103
96
  const format = args.format ?? 'webp';
104
97
  const mimeType = `image/${format}`;
@@ -106,8 +99,31 @@ export function createPreviewTool(opts) {
106
99
  const exportOpts = { targetWidth };
107
100
  if (args.height !== undefined)
108
101
  exportOpts.targetHeight = Math.min(args.height, MAX_DIMENSION_PX);
109
- const blob = await rec.time('engine_execute', () => eng.block.export(args.blockId, mimeType, exportOpts));
110
- const bytes = new Uint8Array(await blob.arrayBuffer());
102
+ // We loaded `revId` under the lock immediately above, so the correct
103
+ // scene is guaranteed to be current here. If export still throws,
104
+ // `blockId` genuinely isn't in this revision's scene — a wrong id, or
105
+ // a block removed in this revision. Return a legible tool error the
106
+ // model can act on — mirroring `edit`'s dirty-on-throw path — instead
107
+ // of rethrowing into an opaque MCP -32603 the model can't reason about.
108
+ let bytes;
109
+ try {
110
+ const blob = await rec.time('engine_execute', () => eng.block.export(args.blockId, mimeType, exportOpts));
111
+ bytes = new Uint8Array(await blob.arrayBuffer());
112
+ }
113
+ catch (err) {
114
+ exportError = err;
115
+ return {
116
+ isError: true,
117
+ content: [
118
+ {
119
+ type: 'text',
120
+ text: `preview: could not render block ${args.blockId} in revision ${revId}. That block id isn't in this revision's scene — double-check the id (use \`inspect\` to see the revision's structure), or it may have been removed in this revision. ${err instanceof Error
121
+ ? (err.stack ?? err.message)
122
+ : String(err)}`
123
+ }
124
+ ]
125
+ };
126
+ }
111
127
  return {
112
128
  content: [
113
129
  { type: 'image', data: bytesToBase64(bytes), mimeType }
@@ -130,7 +146,11 @@ export function createPreviewTool(opts) {
130
146
  args: rawArgs,
131
147
  ...(result !== undefined ? { result: summarizeResult(result) } : {}),
132
148
  ...(revision ? { revision } : {}),
133
- ...(threw ? { error: toErrorShape(threw) } : {})
149
+ ...(threw
150
+ ? { error: toErrorShape(threw) }
151
+ : exportError
152
+ ? { error: toErrorShape(exportError) }
153
+ : {})
134
154
  });
135
155
  }
136
156
  }
@@ -1 +1 @@
1
- {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/tools/preview.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/codesign-mcp/src/tools/preview.ts\n//\n// `preview` — renders a block to an inline image. Adds an optional `revision`\n// (spec §6.1): when supplied and the engine slot is at a different revision,\n// the session loads that revision before exporting. Without `revision`, renders\n// the engine's current state (uses `session.currentSlot()` — side-effect-free).\n\nimport { performance } from 'node:perf_hooks';\nimport type { Mutex } from '../engine/mutex.js';\nimport type { EngineSession, EngineSlot } from '../engine/session.js';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport {\n SpanRecorder,\n NOOP_TELEMETRY_SINK,\n summarizeResult,\n toErrorShape,\n type TelemetrySink\n} from './spans.js';\n\nconst MAX_DIMENSION_PX = 1800;\nconst DEFAULT_TARGET_WIDTH = 512;\n\ninterface MinimalEngine {\n block: {\n export(blockId: number, mimeType: string, options?: unknown): Promise<Blob>;\n };\n}\n\ninterface PreviewArgs {\n blockId: number;\n revision?: string;\n format?: 'webp' | 'png' | 'jpeg';\n width?: number;\n height?: number;\n}\n\nconst DESCRIPTION = [\n 'Render a block (almost always a page) to an inline image so you can see the result.',\n '',\n \"`blockId` is REQUIRED — preview the page you're working on, not the scene root.\",\n '',\n \"Optional `revision`: if supplied, loads that revision into the engine before rendering. Without it, renders the engine's current state (almost always what you want directly after `edit`).\",\n '',\n \"Defaults: format='webp', width=512. Width/height clamped at 1800 px (Anthropic many-image limit).\"\n].join('\\n');\n\nconst PREVIEW_INPUT_SCHEMA = {\n type: 'object' as const,\n properties: {\n blockId: { type: 'integer' as const },\n revision: {\n type: 'string' as const,\n description: \"Revision id; defaults to the engine's current state.\"\n },\n format: { enum: ['webp', 'png', 'jpeg'] },\n width: { type: 'integer' as const, minimum: 1 },\n height: { type: 'integer' as const, minimum: 1 }\n },\n required: ['blockId'],\n additionalProperties: false\n};\n\nexport interface CreatePreviewToolOptions<E = unknown> {\n store: RevisionStore;\n session: EngineSession<E>;\n /** Guards the shared engine — load-then-export must not interleave with another session's edit (see engine/mutex.ts). */\n engineLock: Mutex;\n /** Diagnostics telemetry port. Absent → no-op (no timing overhead beyond a few clocks). */\n telemetry?: TelemetrySink;\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== 'undefined')\n return Buffer.from(bytes).toString('base64');\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n return btoa(s);\n}\n\nexport function createPreviewTool<E = unknown>(\n opts: CreatePreviewToolOptions<E>\n): CodesignMcpToolRegistration {\n const { store, session, engineLock } = opts;\n const telemetry = opts.telemetry ?? NOOP_TELEMETRY_SINK;\n return {\n name: 'preview',\n description: DESCRIPTION,\n inputSchema: PREVIEW_INPUT_SCHEMA,\n call: async (rawArgs) => {\n const rec = new SpanRecorder();\n const startedAt = performance.now();\n type CallResult = Awaited<\n ReturnType<CodesignMcpToolRegistration['call']>\n >;\n let result: CallResult | undefined;\n let threw: unknown;\n let revision: string | undefined;\n try {\n result = await (async (): Promise<CallResult> => {\n const args = (rawArgs ?? {}) as PreviewArgs;\n if (typeof args.blockId !== 'number') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: `blockId` (integer) is required.'\n }\n ]\n };\n }\n // Load (if a revision was given) and export read the shared engine across\n // awaits — serialize against any concurrent engine-touching tool call.\n const beforeLock = performance.now();\n return engineLock.runExclusive(async () => {\n rec.add('lock_wait', performance.now() - beforeLock);\n let slot: EngineSlot<E>;\n if (args.revision !== undefined) {\n const revId = args.revision;\n revision = revId;\n const rev = await rec.time('workspace_read', () =>\n store.read(revId)\n );\n slot = await rec.time('engine_scene_load', async () => {\n await session.populate(revId, rev.scene);\n return session.acquire(revId);\n });\n } else {\n // No revision: render the current engine state via the side-effect-free\n // currentSlot accessor (does NOT reset). If no slot is warm, error.\n const current = session.currentSlot();\n if (!current) {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: no engine state to render. Run `edit` first, or pass `revision` to load a specific revision.'\n }\n ]\n };\n }\n slot = current;\n }\n const eng = slot.engine as MinimalEngine;\n const format = args.format ?? 'webp';\n const mimeType = `image/${format}`;\n const targetWidth = Math.min(\n args.width ?? DEFAULT_TARGET_WIDTH,\n MAX_DIMENSION_PX\n );\n const exportOpts: Record<string, unknown> = { targetWidth };\n if (args.height !== undefined)\n exportOpts.targetHeight = Math.min(args.height, MAX_DIMENSION_PX);\n const blob = await rec.time('engine_execute', () =>\n eng.block.export(args.blockId, mimeType, exportOpts)\n );\n const bytes = new Uint8Array(await blob.arrayBuffer());\n return {\n content: [\n { type: 'image' as const, data: bytesToBase64(bytes), mimeType }\n ]\n };\n });\n })();\n return result;\n } catch (err) {\n threw = err;\n throw err;\n } finally {\n telemetry.record({\n tool: 'preview',\n succeeded: threw === undefined && result?.isError !== true,\n totalMs: performance.now() - startedAt,\n spans: rec.list(),\n args: rawArgs,\n ...(result !== undefined ? { result: summarizeResult(result) } : {}),\n ...(revision ? { revision } : {}),\n ...(threw ? { error: toErrorShape(threw) } : {})\n });\n }\n }\n };\n}\n"]}
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/tools/preview.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,yEAAyE;AACzE,4EAA4E;AAC5E,8EAA8E;AAC9E,oEAAoE;AAEpE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAK9C,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,YAAY,EAEb,MAAM,YAAY,CAAC;AAEpB,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAgBjC,MAAM,WAAW,GAAG;IAClB,qFAAqF;IACrF,EAAE;IACF,6KAA6K;IAC7K,EAAE;IACF,4LAA4L;IAC5L,EAAE;IACF,mGAAmG;CACpG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE,QAAiB;IACvB,UAAU,EAAE;QACV,OAAO,EAAE,EAAE,IAAI,EAAE,SAAkB,EAAE;QACrC,QAAQ,EAAE;YACR,IAAI,EAAE,QAAiB;YACvB,WAAW,EACT,wHAAwH;SAC3H;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE;QACzC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAkB,EAAE,OAAO,EAAE,CAAC,EAAE;QAC/C,MAAM,EAAE,EAAE,IAAI,EAAE,SAAkB,EAAE,OAAO,EAAE,CAAC,EAAE;KACjD;IACD,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IACjC,oBAAoB,EAAE,KAAK;CAC5B,CAAC;AAWF,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,OAAO,MAAM,KAAK,WAAW;QAC/B,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAiC;IAEjC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,mBAAmB,CAAC;IACxD,OAAO;QACL,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,oBAAoB;QACjC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACtB,MAAM,GAAG,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAIpC,IAAI,MAA8B,CAAC;YACnC,IAAI,KAAc,CAAC;YACnB,IAAI,QAA4B,CAAC;YACjC,IAAI,WAAoB,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,CAAC,KAAK,IAAyB,EAAE;oBAC9C,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAgB,CAAC;oBAC5C,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;wBACrC,OAAO;4BACL,OAAO,EAAE,IAAI;4BACb,OAAO,EAAE;gCACP;oCACE,IAAI,EAAE,MAAe;oCACrB,IAAI,EAAE,2CAA2C;iCAClD;6BACF;yBACF,CAAC;oBACJ,CAAC;oBACD,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;wBACtC,OAAO;4BACL,OAAO,EAAE,IAAI;4BACb,OAAO,EAAE;gCACP;oCACE,IAAI,EAAE,MAAe;oCACrB,IAAI,EAAE,iMAAiM;iCACxM;6BACF;yBACF,CAAC;oBACJ,CAAC;oBACD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;oBAC5B,QAAQ,GAAG,KAAK,CAAC;oBACjB,yEAAyE;oBACzE,0EAA0E;oBAC1E,8BAA8B;oBAC9B,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;oBACrC,OAAO,UAAU,CAAC,YAAY,CAAC,KAAK,IAAI,EAAE;wBACxC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,CAAC;wBACrD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAChD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAClB,CAAC;wBACF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;4BAC1D,MAAM,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;4BACzC,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;wBAChC,CAAC,CAAC,CAAC;wBACH,MAAM,GAAG,GAAG,IAAI,CAAC,MAAuB,CAAC;wBACzC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC;wBACrC,MAAM,QAAQ,GAAG,SAAS,MAAM,EAAE,CAAC;wBACnC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAC1B,IAAI,CAAC,KAAK,IAAI,oBAAoB,EAClC,gBAAgB,CACjB,CAAC;wBACF,MAAM,UAAU,GAA4B,EAAE,WAAW,EAAE,CAAC;wBAC5D,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;4BAC3B,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;wBACpE,qEAAqE;wBACrE,kEAAkE;wBAClE,sEAAsE;wBACtE,oEAAoE;wBACpE,sEAAsE;wBACtE,wEAAwE;wBACxE,IAAI,KAAiB,CAAC;wBACtB,IAAI,CAAC;4BACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,CACjD,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CACrD,CAAC;4BACF,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;wBACnD,CAAC;wBAAC,OAAO,GAAG,EAAE,CAAC;4BACb,WAAW,GAAG,GAAG,CAAC;4BAClB,OAAO;gCACL,OAAO,EAAE,IAAI;gCACb,OAAO,EAAE;oCACP;wCACE,IAAI,EAAE,MAAe;wCACrB,IAAI,EAAE,mCAAmC,IAAI,CAAC,OAAO,gBAAgB,KAAK,yKACxE,GAAG,YAAY,KAAK;4CAClB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC;4CAC5B,CAAC,CAAC,MAAM,CAAC,GAAG,CAChB,EAAE;qCACH;iCACF;6BACF,CAAC;wBACJ,CAAC;wBACD,OAAO;4BACL,OAAO,EAAE;gCACP,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE;6BACjE;yBACF,CAAC;oBACJ,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC,EAAE,CAAC;gBACL,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,KAAK,GAAG,GAAG,CAAC;gBACZ,MAAM,GAAG,CAAC;YACZ,CAAC;oBAAS,CAAC;gBACT,SAAS,CAAC,MAAM,CAAC;oBACf,IAAI,EAAE,SAAS;oBACf,SAAS,EAAE,KAAK,KAAK,SAAS,IAAI,MAAM,EAAE,OAAO,KAAK,IAAI;oBAC1D,OAAO,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS;oBACtC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE;oBACjB,IAAI,EAAE,OAAO;oBACb,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACpE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjC,GAAG,CAAC,KAAK;wBACP,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE;wBAChC,CAAC,CAAC,WAAW;4BACX,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,WAAW,CAAC,EAAE;4BACtC,CAAC,CAAC,EAAE,CAAC;iBACV,CAAC,CAAC;YACL,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// packages/codesign-mcp/src/tools/preview.ts\n//\n// `preview` — renders a block to an inline image. `revision` is REQUIRED\n// (CODE-76): the session loads that revision before exporting, so `blockId`\n// always resolves against the scene it belongs to — never against a different\n// design a concurrent agent may have loaded into the shared engine.\n\nimport { performance } from 'node:perf_hooks';\nimport type { Mutex } from '../engine/mutex.js';\nimport type { EngineSession } from '../engine/session.js';\nimport type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport {\n SpanRecorder,\n NOOP_TELEMETRY_SINK,\n summarizeResult,\n toErrorShape,\n type TelemetrySink\n} from './spans.js';\n\nconst MAX_DIMENSION_PX = 1800;\nconst DEFAULT_TARGET_WIDTH = 512;\n\ninterface MinimalEngine {\n block: {\n export(blockId: number, mimeType: string, options?: unknown): Promise<Blob>;\n };\n}\n\ninterface PreviewArgs {\n blockId: number;\n revision: string;\n format?: 'webp' | 'png' | 'jpeg';\n width?: number;\n height?: number;\n}\n\nconst DESCRIPTION = [\n 'Render a block (almost always a page) to an inline image so you can see the result.',\n '',\n \"`blockId` and `revision` are BOTH REQUIRED. Preview the page you're working on (not the scene root), and pass the `revision` the block belongs to — the id `edit` returned.\",\n '',\n 'The given `revision` is loaded before rendering, so `blockId` always resolves against the right scene — never against a different design another agent may have loaded on a shared server.',\n '',\n \"Defaults: format='webp', width=512. Width/height clamped at 1800 px (Anthropic many-image limit).\"\n].join('\\n');\n\nconst PREVIEW_INPUT_SCHEMA = {\n type: 'object' as const,\n properties: {\n blockId: { type: 'integer' as const },\n revision: {\n type: 'string' as const,\n description:\n 'Revision id the block belongs to (from `edit`). Loaded before rendering so `blockId` resolves against the right scene.'\n },\n format: { enum: ['webp', 'png', 'jpeg'] },\n width: { type: 'integer' as const, minimum: 1 },\n height: { type: 'integer' as const, minimum: 1 }\n },\n required: ['blockId', 'revision'],\n additionalProperties: false\n};\n\nexport interface CreatePreviewToolOptions<E = unknown> {\n store: RevisionStore;\n session: EngineSession<E>;\n /** Guards the shared engine — load-then-export must not interleave with another session's edit (see engine/mutex.ts). */\n engineLock: Mutex;\n /** Diagnostics telemetry port. Absent → no-op (no timing overhead beyond a few clocks). */\n telemetry?: TelemetrySink;\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== 'undefined')\n return Buffer.from(bytes).toString('base64');\n let s = '';\n for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n return btoa(s);\n}\n\nexport function createPreviewTool<E = unknown>(\n opts: CreatePreviewToolOptions<E>\n): CodesignMcpToolRegistration {\n const { store, session, engineLock } = opts;\n const telemetry = opts.telemetry ?? NOOP_TELEMETRY_SINK;\n return {\n name: 'preview',\n description: DESCRIPTION,\n inputSchema: PREVIEW_INPUT_SCHEMA,\n call: async (rawArgs) => {\n const rec = new SpanRecorder();\n const startedAt = performance.now();\n type CallResult = Awaited<\n ReturnType<CodesignMcpToolRegistration['call']>\n >;\n let result: CallResult | undefined;\n let threw: unknown;\n let revision: string | undefined;\n let exportError: unknown;\n try {\n result = await (async (): Promise<CallResult> => {\n const args = (rawArgs ?? {}) as PreviewArgs;\n if (typeof args.blockId !== 'number') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: `blockId` (integer) is required.'\n }\n ]\n };\n }\n if (typeof args.revision !== 'string') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'preview: `revision` (string) is required — pass the revision the block belongs to (the id `edit` returned). It is loaded before rendering so `blockId` always resolves against the right scene.'\n }\n ]\n };\n }\n const revId = args.revision;\n revision = revId;\n // Load the requested revision, then export — both read the shared engine\n // across awaits, so serialize against any concurrent engine-touching tool\n // call (see engine/mutex.ts).\n const beforeLock = performance.now();\n return engineLock.runExclusive(async () => {\n rec.add('lock_wait', performance.now() - beforeLock);\n const rev = await rec.time('workspace_read', () =>\n store.read(revId)\n );\n const slot = await rec.time('engine_scene_load', async () => {\n await session.populate(revId, rev.scene);\n return session.acquire(revId);\n });\n const eng = slot.engine as MinimalEngine;\n const format = args.format ?? 'webp';\n const mimeType = `image/${format}`;\n const targetWidth = Math.min(\n args.width ?? DEFAULT_TARGET_WIDTH,\n MAX_DIMENSION_PX\n );\n const exportOpts: Record<string, unknown> = { targetWidth };\n if (args.height !== undefined)\n exportOpts.targetHeight = Math.min(args.height, MAX_DIMENSION_PX);\n // We loaded `revId` under the lock immediately above, so the correct\n // scene is guaranteed to be current here. If export still throws,\n // `blockId` genuinely isn't in this revision's scene — a wrong id, or\n // a block removed in this revision. Return a legible tool error the\n // model can act on — mirroring `edit`'s dirty-on-throw path — instead\n // of rethrowing into an opaque MCP -32603 the model can't reason about.\n let bytes: Uint8Array;\n try {\n const blob = await rec.time('engine_execute', () =>\n eng.block.export(args.blockId, mimeType, exportOpts)\n );\n bytes = new Uint8Array(await blob.arrayBuffer());\n } catch (err) {\n exportError = err;\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: `preview: could not render block ${args.blockId} in revision ${revId}. That block id isn't in this revision's scene — double-check the id (use \\`inspect\\` to see the revision's structure), or it may have been removed in this revision. ${\n err instanceof Error\n ? (err.stack ?? err.message)\n : String(err)\n }`\n }\n ]\n };\n }\n return {\n content: [\n { type: 'image' as const, data: bytesToBase64(bytes), mimeType }\n ]\n };\n });\n })();\n return result;\n } catch (err) {\n threw = err;\n throw err;\n } finally {\n telemetry.record({\n tool: 'preview',\n succeeded: threw === undefined && result?.isError !== true,\n totalMs: performance.now() - startedAt,\n spans: rec.list(),\n args: rawArgs,\n ...(result !== undefined ? { result: summarizeResult(result) } : {}),\n ...(revision ? { revision } : {}),\n ...(threw\n ? { error: toErrorShape(threw) }\n : exportError\n ? { error: toErrorShape(exportError) }\n : {})\n });\n }\n }\n };\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import type { RevisionStore } from '../revisions/store.js';
2
2
  import type { CodesignMcpToolRegistration } from '../server.js';
3
3
  import type { ViewerUrlBuilder } from '../viewer/url-builder.js';
4
- export interface CreateOpenEditorToolOptions {
4
+ export interface CreateViewToolOptions {
5
5
  urlBuilder: ViewerUrlBuilder;
6
6
  /** Resolves a revision to its design root (the root revision id). */
7
7
  store: RevisionStore;
8
8
  }
9
- export declare function createOpenEditorTool(opts: CreateOpenEditorToolOptions): CodesignMcpToolRegistration;
10
- //# sourceMappingURL=open-editor.d.ts.map
9
+ export declare function createViewTool(opts: CreateViewToolOptions): CodesignMcpToolRegistration;
10
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/tools/view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,qEAAqE;IACrE,KAAK,EAAE,aAAa,CAAC;CACtB;AASD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,qBAAqB,GAC1B,2BAA2B,CA2D7B"}
@@ -1,39 +1,41 @@
1
- const DESCRIPTION = "Get a URL the human can open to view the design in CE.SDK. With `revision`, returns the design's " +
1
+ const DESCRIPTION = "Get a URL the human can open to view the design in CE.SDK. Returns the design's " +
2
2
  "stable URL — the viewer keeps it live, re-rendering as you `edit`, so print it ONCE and don't re-emit " +
3
3
  "a fresh URL every turn. Add `pin: true` for a frozen permalink to that exact revision (for 'look at " +
4
- "exactly this' or comparing revisions). Omit `revision` for the workspace landing page (mini-studio). " +
5
- 'The viewer is read-only — agent edits remain authoritative.';
6
- export function createOpenEditorTool(opts) {
4
+ "exactly this' or comparing revisions). " +
5
+ 'The page is a full editor; edits made in the browser stay local to that tab (export-only) — agent edits remain authoritative.';
6
+ export function createViewTool(opts) {
7
7
  return {
8
- name: 'open_editor',
8
+ name: 'view',
9
9
  description: DESCRIPTION,
10
10
  inputSchema: {
11
11
  type: 'object',
12
12
  properties: {
13
13
  revision: {
14
14
  type: 'string',
15
- description: 'Any revision in the design. Omit to get the workspace landing page.'
15
+ description: 'Any revision in the design; resolved to its root and opened at the design head.'
16
16
  },
17
17
  pin: {
18
18
  type: 'boolean',
19
19
  description: 'When true, return a frozen permalink to this exact revision instead of the live design URL.'
20
20
  }
21
21
  },
22
+ required: ['revision'],
22
23
  additionalProperties: false
23
24
  },
24
25
  call: async (rawArgs) => {
25
26
  const args = (rawArgs ?? {});
27
+ if (typeof args.revision !== 'string') {
28
+ return {
29
+ isError: true,
30
+ content: [
31
+ {
32
+ type: 'text',
33
+ text: 'view: `revision` (string) is required.'
34
+ }
35
+ ]
36
+ };
37
+ }
26
38
  try {
27
- if (args.revision === undefined) {
28
- return {
29
- content: [
30
- {
31
- type: 'text',
32
- text: JSON.stringify({ url: opts.urlBuilder.forStudio() })
33
- }
34
- ]
35
- };
36
- }
37
39
  // historyOf returns the chain root-first; [0] is the design root.
38
40
  const chain = await opts.store.historyOf(args.revision);
39
41
  const root = chain[0].id;
@@ -53,10 +55,10 @@ export function createOpenEditorTool(opts) {
53
55
  : String(err);
54
56
  return {
55
57
  isError: true,
56
- content: [{ type: 'text', text: `open_editor: ${msg}` }]
58
+ content: [{ type: 'text', text: `view: ${msg}` }]
57
59
  };
58
60
  }
59
61
  }
60
62
  };
61
63
  }
62
- //# sourceMappingURL=open-editor.js.map
64
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/tools/view.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,GACf,kFAAkF;IAClF,wGAAwG;IACxG,sGAAsG;IACtG,yCAAyC;IACzC,+HAA+H,CAAC;AAElI,MAAM,UAAU,cAAc,CAC5B,IAA2B;IAE3B,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE;YACX,IAAI,EAAE,QAAiB;YACvB,UAAU,EAAE;gBACV,QAAQ,EAAE;oBACR,IAAI,EAAE,QAAiB;oBACvB,WAAW,EACT,iFAAiF;iBACpF;gBACD,GAAG,EAAE;oBACH,IAAI,EAAE,SAAkB;oBACxB,WAAW,EACT,6FAA6F;iBAChG;aACF;YACD,QAAQ,EAAE,CAAC,UAAU,CAAC;YACtB,oBAAoB,EAAE,KAAK;SAC5B;QACD,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACtB,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAyC,CAAC;YACrE,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACtC,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,wCAAwC;yBAC/C;qBACF;iBACF,CAAC;YACJ,CAAC;YACD,IAAI,CAAC;gBACH,kEAAkE;gBAClE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACxD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG;oBAClB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC;oBACxD,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBACpC,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;iBACpE,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,IAAI,GAAI,GAAyB,CAAC,IAAI,CAAC;gBAC7C,MAAM,GAAG,GACP,IAAI,KAAK,QAAQ;oBACf,CAAC,CAAC,oBAAoB,IAAI,CAAC,QAAQ,EAAE;oBACrC,CAAC,CAAC,GAAG,YAAY,KAAK;wBACpB,CAAC,CAAC,GAAG,CAAC,OAAO;wBACb,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACpB,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,EAAE,CAAC;iBAC3D,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { RevisionStore } from '../revisions/store.js';\nimport type { CodesignMcpToolRegistration } from '../server.js';\nimport type { ViewerUrlBuilder } from '../viewer/url-builder.js';\n\nexport interface CreateViewToolOptions {\n urlBuilder: ViewerUrlBuilder;\n /** Resolves a revision to its design root (the root revision id). */\n store: RevisionStore;\n}\n\nconst DESCRIPTION =\n \"Get a URL the human can open to view the design in CE.SDK. Returns the design's \" +\n \"stable URL — the viewer keeps it live, re-rendering as you `edit`, so print it ONCE and don't re-emit \" +\n \"a fresh URL every turn. Add `pin: true` for a frozen permalink to that exact revision (for 'look at \" +\n \"exactly this' or comparing revisions). \" +\n 'The page is a full editor; edits made in the browser stay local to that tab (export-only) — agent edits remain authoritative.';\n\nexport function createViewTool(\n opts: CreateViewToolOptions\n): CodesignMcpToolRegistration {\n return {\n name: 'view',\n description: DESCRIPTION,\n inputSchema: {\n type: 'object' as const,\n properties: {\n revision: {\n type: 'string' as const,\n description:\n 'Any revision in the design; resolved to its root and opened at the design head.'\n },\n pin: {\n type: 'boolean' as const,\n description:\n 'When true, return a frozen permalink to this exact revision instead of the live design URL.'\n }\n },\n required: ['revision'],\n additionalProperties: false\n },\n call: async (rawArgs) => {\n const args = (rawArgs ?? {}) as { revision?: string; pin?: boolean };\n if (typeof args.revision !== 'string') {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: 'view: `revision` (string) is required.'\n }\n ]\n };\n }\n try {\n // historyOf returns the chain root-first; [0] is the design root.\n const chain = await opts.store.historyOf(args.revision);\n const root = chain[0].id;\n const url = args.pin\n ? opts.urlBuilder.forPinnedRevision(root, args.revision)\n : opts.urlBuilder.forDesign(root);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ url }) }]\n };\n } catch (err) {\n const code = (err as { code?: string }).code;\n const msg =\n code === 'ENOENT'\n ? `unknown revision ${args.revision}`\n : err instanceof Error\n ? err.message\n : String(err);\n return {\n isError: true,\n content: [{ type: 'text' as const, text: `view: ${msg}` }]\n };\n }\n }\n };\n}\n"]}
@@ -9,6 +9,8 @@ export interface ViewerListener {
9
9
  }
10
10
  export interface StartViewerListenerOptions {
11
11
  router: Router;
12
+ /** Additional routers mounted after the main router (e.g. the diagnostics router). */
13
+ extraRouters?: Router[];
12
14
  host?: string;
13
15
  port?: number;
14
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/viewer/listener.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,cAAc,CAAC,CAuBzB"}
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/viewer/listener.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,cAAc,CAAC,CAwBzB"}
@@ -8,6 +8,8 @@ export async function startViewerListener(opts) {
8
8
  const host = opts.host ?? '127.0.0.1';
9
9
  const app = express();
10
10
  app.use(opts.router);
11
+ for (const extra of opts.extraRouters ?? [])
12
+ app.use(extra);
11
13
  const server = app.listen(opts.port ?? 0, host);
12
14
  await new Promise((resolve, reject) => {
13
15
  server.once('listening', () => resolve());