@things-factory/board-ai 10.0.0-beta.108

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 (329) hide show
  1. package/PLAN-styling-tools.md +1253 -0
  2. package/client/components/board-ai-chat.test.ts +149 -0
  3. package/client/components/board-ai-chat.ts +2574 -0
  4. package/client/components/chat-echo-dedup.test.ts +116 -0
  5. package/client/components/chat-echo-dedup.ts +89 -0
  6. package/client/components/chat-input-builder.ts +47 -0
  7. package/client/components/default-catalog-entries.test.ts +59 -0
  8. package/client/components/default-catalog-entries.ts +100 -0
  9. package/client/components/default-slash-templates.test.ts +55 -0
  10. package/client/components/default-slash-templates.ts +129 -0
  11. package/client/components/markdown.test.ts +518 -0
  12. package/client/components/markdown.ts +508 -0
  13. package/client/components/mention-popup-helpers.ts +433 -0
  14. package/client/components/mention-popup.test.ts +588 -0
  15. package/client/components/mention-popup.ts +238 -0
  16. package/client/index.ts +11 -0
  17. package/client/tsconfig.json +13 -0
  18. package/client/utils/board-edit-patch.ts +374 -0
  19. package/config/config.development.js +43 -0
  20. package/config/config.production.js +15 -0
  21. package/dist-client/client/components/board-ai-chat.d.ts +277 -0
  22. package/dist-client/client/components/board-ai-chat.js +2444 -0
  23. package/dist-client/client/components/board-ai-chat.js.map +1 -0
  24. package/dist-client/client/components/board-ai-chat.test.d.ts +1 -0
  25. package/dist-client/client/components/board-ai-chat.test.js +138 -0
  26. package/dist-client/client/components/board-ai-chat.test.js.map +1 -0
  27. package/dist-client/client/components/chat-echo-dedup.d.ts +45 -0
  28. package/dist-client/client/components/chat-echo-dedup.js +58 -0
  29. package/dist-client/client/components/chat-echo-dedup.js.map +1 -0
  30. package/dist-client/client/components/chat-echo-dedup.test.d.ts +1 -0
  31. package/dist-client/client/components/chat-echo-dedup.test.js +101 -0
  32. package/dist-client/client/components/chat-echo-dedup.test.js.map +1 -0
  33. package/dist-client/client/components/chat-input-builder.d.ts +39 -0
  34. package/dist-client/client/components/chat-input-builder.js +26 -0
  35. package/dist-client/client/components/chat-input-builder.js.map +1 -0
  36. package/dist-client/client/components/default-catalog-entries.d.ts +15 -0
  37. package/dist-client/client/components/default-catalog-entries.js +86 -0
  38. package/dist-client/client/components/default-catalog-entries.js.map +1 -0
  39. package/dist-client/client/components/default-catalog-entries.test.d.ts +1 -0
  40. package/dist-client/client/components/default-catalog-entries.test.js +52 -0
  41. package/dist-client/client/components/default-catalog-entries.test.js.map +1 -0
  42. package/dist-client/client/components/default-slash-templates.d.ts +17 -0
  43. package/dist-client/client/components/default-slash-templates.js +113 -0
  44. package/dist-client/client/components/default-slash-templates.js.map +1 -0
  45. package/dist-client/client/components/default-slash-templates.test.d.ts +1 -0
  46. package/dist-client/client/components/default-slash-templates.test.js +51 -0
  47. package/dist-client/client/components/default-slash-templates.test.js.map +1 -0
  48. package/dist-client/client/components/markdown.d.ts +60 -0
  49. package/dist-client/client/components/markdown.js +422 -0
  50. package/dist-client/client/components/markdown.js.map +1 -0
  51. package/dist-client/client/components/markdown.test.d.ts +1 -0
  52. package/dist-client/client/components/markdown.test.js +418 -0
  53. package/dist-client/client/components/markdown.test.js.map +1 -0
  54. package/dist-client/client/components/mention-popup-helpers.d.ts +165 -0
  55. package/dist-client/client/components/mention-popup-helpers.js +330 -0
  56. package/dist-client/client/components/mention-popup-helpers.js.map +1 -0
  57. package/dist-client/client/components/mention-popup.d.ts +35 -0
  58. package/dist-client/client/components/mention-popup.js +223 -0
  59. package/dist-client/client/components/mention-popup.js.map +1 -0
  60. package/dist-client/client/components/mention-popup.test.d.ts +1 -0
  61. package/dist-client/client/components/mention-popup.test.js +474 -0
  62. package/dist-client/client/components/mention-popup.test.js.map +1 -0
  63. package/dist-client/client/index.d.ts +11 -0
  64. package/dist-client/client/index.js +12 -0
  65. package/dist-client/client/index.js.map +1 -0
  66. package/dist-client/client/utils/board-edit-patch.d.ts +157 -0
  67. package/dist-client/client/utils/board-edit-patch.js +284 -0
  68. package/dist-client/client/utils/board-edit-patch.js.map +1 -0
  69. package/dist-client/server/service/agentic-loop.d.ts +87 -0
  70. package/dist-client/server/service/agentic-loop.js +276 -0
  71. package/dist-client/server/service/agentic-loop.js.map +1 -0
  72. package/dist-client/server/service/assistant.d.ts +84 -0
  73. package/dist-client/server/service/assistant.js +2220 -0
  74. package/dist-client/server/service/assistant.js.map +1 -0
  75. package/dist-client/server/service/catalog-resolver.d.ts +18 -0
  76. package/dist-client/server/service/catalog-resolver.js +93 -0
  77. package/dist-client/server/service/catalog-resolver.js.map +1 -0
  78. package/dist-client/server/service/component-tree.d.ts +68 -0
  79. package/dist-client/server/service/component-tree.js +68 -0
  80. package/dist-client/server/service/component-tree.js.map +1 -0
  81. package/dist-client/server/service/mention-resolver.d.ts +96 -0
  82. package/dist-client/server/service/mention-resolver.js +172 -0
  83. package/dist-client/server/service/mention-resolver.js.map +1 -0
  84. package/dist-client/server/service/prefab-registry.d.ts +73 -0
  85. package/dist-client/server/service/prefab-registry.js +252 -0
  86. package/dist-client/server/service/prefab-registry.js.map +1 -0
  87. package/dist-client/server/service/styling/clear-tools.d.ts +15 -0
  88. package/dist-client/server/service/styling/clear-tools.js +50 -0
  89. package/dist-client/server/service/styling/clear-tools.js.map +1 -0
  90. package/dist-client/server/service/styling/color-utils.d.ts +43 -0
  91. package/dist-client/server/service/styling/color-utils.js +205 -0
  92. package/dist-client/server/service/styling/color-utils.js.map +1 -0
  93. package/dist-client/server/service/styling/copy-tools.d.ts +27 -0
  94. package/dist-client/server/service/styling/copy-tools.js +78 -0
  95. package/dist-client/server/service/styling/copy-tools.js.map +1 -0
  96. package/dist-client/server/service/styling/effect-tools.d.ts +45 -0
  97. package/dist-client/server/service/styling/effect-tools.js +85 -0
  98. package/dist-client/server/service/styling/effect-tools.js.map +1 -0
  99. package/dist-client/server/service/styling/fill-tools.d.ts +60 -0
  100. package/dist-client/server/service/styling/fill-tools.js +226 -0
  101. package/dist-client/server/service/styling/fill-tools.js.map +1 -0
  102. package/dist-client/server/service/styling/material-tools.d.ts +32 -0
  103. package/dist-client/server/service/styling/material-tools.js +88 -0
  104. package/dist-client/server/service/styling/material-tools.js.map +1 -0
  105. package/dist-client/server/service/styling/read-tools.d.ts +34 -0
  106. package/dist-client/server/service/styling/read-tools.js +42 -0
  107. package/dist-client/server/service/styling/read-tools.js.map +1 -0
  108. package/dist-client/server/service/styling/registry.d.ts +91 -0
  109. package/dist-client/server/service/styling/registry.js +345 -0
  110. package/dist-client/server/service/styling/registry.js.map +1 -0
  111. package/dist-client/server/service/styling/stroke-tools.d.ts +35 -0
  112. package/dist-client/server/service/styling/stroke-tools.js +94 -0
  113. package/dist-client/server/service/styling/stroke-tools.js.map +1 -0
  114. package/dist-client/server/service/styling/text-tools.d.ts +32 -0
  115. package/dist-client/server/service/styling/text-tools.js +101 -0
  116. package/dist-client/server/service/styling/text-tools.js.map +1 -0
  117. package/dist-client/server/service/types.d.ts +265 -0
  118. package/dist-client/server/service/types.js +2 -0
  119. package/dist-client/server/service/types.js.map +1 -0
  120. package/dist-client/server/service/validation/board-model-schema.d.ts +8311 -0
  121. package/dist-client/server/service/validation/board-model-schema.js +402 -0
  122. package/dist-client/server/service/validation/board-model-schema.js.map +1 -0
  123. package/dist-client/server/service/validation/tool-validation.d.ts +31 -0
  124. package/dist-client/server/service/validation/tool-validation.js +193 -0
  125. package/dist-client/server/service/validation/tool-validation.js.map +1 -0
  126. package/dist-client/tsconfig.tsbuildinfo +1 -0
  127. package/dist-server/index.d.ts +23 -0
  128. package/dist-server/index.js +27 -0
  129. package/dist-server/index.js.map +1 -0
  130. package/dist-server/service/agentic-loop.d.ts +87 -0
  131. package/dist-server/service/agentic-loop.js +280 -0
  132. package/dist-server/service/agentic-loop.js.map +1 -0
  133. package/dist-server/service/apply-patch.d.ts +47 -0
  134. package/dist-server/service/apply-patch.js +369 -0
  135. package/dist-server/service/apply-patch.js.map +1 -0
  136. package/dist-server/service/assistant.d.ts +84 -0
  137. package/dist-server/service/assistant.js +2237 -0
  138. package/dist-server/service/assistant.js.map +1 -0
  139. package/dist-server/service/board-ai-resolver.d.ts +54 -0
  140. package/dist-server/service/board-ai-resolver.js +409 -0
  141. package/dist-server/service/board-ai-resolver.js.map +1 -0
  142. package/dist-server/service/catalog-resolver.d.ts +18 -0
  143. package/dist-server/service/catalog-resolver.js +98 -0
  144. package/dist-server/service/catalog-resolver.js.map +1 -0
  145. package/dist-server/service/chat-message/chat-message-broadcast.d.ts +13 -0
  146. package/dist-server/service/chat-message/chat-message-broadcast.js +13 -0
  147. package/dist-server/service/chat-message/chat-message-broadcast.js.map +1 -0
  148. package/dist-server/service/chat-message/chat-message-publish.d.ts +6 -0
  149. package/dist-server/service/chat-message/chat-message-publish.js +27 -0
  150. package/dist-server/service/chat-message/chat-message-publish.js.map +1 -0
  151. package/dist-server/service/chat-message/chat-message-subscription.d.ts +8 -0
  152. package/dist-server/service/chat-message/chat-message-subscription.js +53 -0
  153. package/dist-server/service/chat-message/chat-message-subscription.js.map +1 -0
  154. package/dist-server/service/chat-message/chat-message.d.ts +56 -0
  155. package/dist-server/service/chat-message/chat-message.js +149 -0
  156. package/dist-server/service/chat-message/chat-message.js.map +1 -0
  157. package/dist-server/service/chat-message/index.d.ts +5 -0
  158. package/dist-server/service/chat-message/index.js +12 -0
  159. package/dist-server/service/chat-message/index.js.map +1 -0
  160. package/dist-server/service/chat-session/chat-session.d.ts +31 -0
  161. package/dist-server/service/chat-session/chat-session.js +131 -0
  162. package/dist-server/service/chat-session/chat-session.js.map +1 -0
  163. package/dist-server/service/chat-session/index.d.ts +3 -0
  164. package/dist-server/service/chat-session/index.js +7 -0
  165. package/dist-server/service/chat-session/index.js.map +1 -0
  166. package/dist-server/service/chat-session-participant/chat-session-participant.d.ts +30 -0
  167. package/dist-server/service/chat-session-participant/chat-session-participant.js +112 -0
  168. package/dist-server/service/chat-session-participant/chat-session-participant.js.map +1 -0
  169. package/dist-server/service/chat-session-participant/index.d.ts +4 -0
  170. package/dist-server/service/chat-session-participant/index.js +10 -0
  171. package/dist-server/service/chat-session-participant/index.js.map +1 -0
  172. package/dist-server/service/chat-session-participant/invite-notification.d.ts +27 -0
  173. package/dist-server/service/chat-session-participant/invite-notification.js +30 -0
  174. package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -0
  175. package/dist-server/service/chat-session-participant/presence.d.ts +4 -0
  176. package/dist-server/service/chat-session-participant/presence.js +22 -0
  177. package/dist-server/service/chat-session-participant/presence.js.map +1 -0
  178. package/dist-server/service/chat-session-resolver.d.ts +20 -0
  179. package/dist-server/service/chat-session-resolver.js +356 -0
  180. package/dist-server/service/chat-session-resolver.js.map +1 -0
  181. package/dist-server/service/component-tree.d.ts +68 -0
  182. package/dist-server/service/component-tree.js +73 -0
  183. package/dist-server/service/component-tree.js.map +1 -0
  184. package/dist-server/service/index.d.ts +20 -0
  185. package/dist-server/service/index.js +34 -0
  186. package/dist-server/service/index.js.map +1 -0
  187. package/dist-server/service/mention-marker.d.ts +78 -0
  188. package/dist-server/service/mention-marker.js +162 -0
  189. package/dist-server/service/mention-marker.js.map +1 -0
  190. package/dist-server/service/mention-resolver.d.ts +96 -0
  191. package/dist-server/service/mention-resolver.js +177 -0
  192. package/dist-server/service/mention-resolver.js.map +1 -0
  193. package/dist-server/service/patch-entry/board-patch-broadcast.d.ts +13 -0
  194. package/dist-server/service/patch-entry/board-patch-broadcast.js +13 -0
  195. package/dist-server/service/patch-entry/board-patch-broadcast.js.map +1 -0
  196. package/dist-server/service/patch-entry/board-patch-subscription.d.ts +12 -0
  197. package/dist-server/service/patch-entry/board-patch-subscription.js +63 -0
  198. package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -0
  199. package/dist-server/service/patch-entry/index.d.ts +4 -0
  200. package/dist-server/service/patch-entry/index.js +12 -0
  201. package/dist-server/service/patch-entry/index.js.map +1 -0
  202. package/dist-server/service/patch-entry/patch-entry.d.ts +16 -0
  203. package/dist-server/service/patch-entry/patch-entry.js +96 -0
  204. package/dist-server/service/patch-entry/patch-entry.js.map +1 -0
  205. package/dist-server/service/prefab-registry.d.ts +73 -0
  206. package/dist-server/service/prefab-registry.js +258 -0
  207. package/dist-server/service/prefab-registry.js.map +1 -0
  208. package/dist-server/service/styling/clear-tools.d.ts +15 -0
  209. package/dist-server/service/styling/clear-tools.js +53 -0
  210. package/dist-server/service/styling/clear-tools.js.map +1 -0
  211. package/dist-server/service/styling/color-utils.d.ts +43 -0
  212. package/dist-server/service/styling/color-utils.js +210 -0
  213. package/dist-server/service/styling/color-utils.js.map +1 -0
  214. package/dist-server/service/styling/copy-tools.d.ts +27 -0
  215. package/dist-server/service/styling/copy-tools.js +81 -0
  216. package/dist-server/service/styling/copy-tools.js.map +1 -0
  217. package/dist-server/service/styling/effect-tools.d.ts +45 -0
  218. package/dist-server/service/styling/effect-tools.js +90 -0
  219. package/dist-server/service/styling/effect-tools.js.map +1 -0
  220. package/dist-server/service/styling/fill-tools.d.ts +60 -0
  221. package/dist-server/service/styling/fill-tools.js +229 -0
  222. package/dist-server/service/styling/fill-tools.js.map +1 -0
  223. package/dist-server/service/styling/material-tools.d.ts +32 -0
  224. package/dist-server/service/styling/material-tools.js +92 -0
  225. package/dist-server/service/styling/material-tools.js.map +1 -0
  226. package/dist-server/service/styling/read-tools.d.ts +34 -0
  227. package/dist-server/service/styling/read-tools.js +45 -0
  228. package/dist-server/service/styling/read-tools.js.map +1 -0
  229. package/dist-server/service/styling/registry.d.ts +91 -0
  230. package/dist-server/service/styling/registry.js +352 -0
  231. package/dist-server/service/styling/registry.js.map +1 -0
  232. package/dist-server/service/styling/stroke-tools.d.ts +35 -0
  233. package/dist-server/service/styling/stroke-tools.js +97 -0
  234. package/dist-server/service/styling/stroke-tools.js.map +1 -0
  235. package/dist-server/service/styling/text-tools.d.ts +32 -0
  236. package/dist-server/service/styling/text-tools.js +105 -0
  237. package/dist-server/service/styling/text-tools.js.map +1 -0
  238. package/dist-server/service/subscribe-auth.d.ts +14 -0
  239. package/dist-server/service/subscribe-auth.js +33 -0
  240. package/dist-server/service/subscribe-auth.js.map +1 -0
  241. package/dist-server/service/types.d.ts +265 -0
  242. package/dist-server/service/types.js +3 -0
  243. package/dist-server/service/types.js.map +1 -0
  244. package/dist-server/service/validation/board-model-schema.d.ts +8311 -0
  245. package/dist-server/service/validation/board-model-schema.js +409 -0
  246. package/dist-server/service/validation/board-model-schema.js.map +1 -0
  247. package/dist-server/service/validation/tool-validation.d.ts +31 -0
  248. package/dist-server/service/validation/tool-validation.js +196 -0
  249. package/dist-server/service/validation/tool-validation.js.map +1 -0
  250. package/dist-server/tsconfig.tsbuildinfo +1 -0
  251. package/package.json +48 -0
  252. package/server/index.ts +23 -0
  253. package/server/service/agentic-loop.test.ts +705 -0
  254. package/server/service/agentic-loop.ts +431 -0
  255. package/server/service/apply-patch-drift.test.ts +829 -0
  256. package/server/service/apply-patch.test.ts +757 -0
  257. package/server/service/apply-patch.ts +411 -0
  258. package/server/service/assistant-integration.test.ts +922 -0
  259. package/server/service/assistant-llm-smoke.test.ts +296 -0
  260. package/server/service/assistant.test.ts +2034 -0
  261. package/server/service/assistant.ts +2378 -0
  262. package/server/service/board-ai-resolver.ts +407 -0
  263. package/server/service/catalog-resolver.test.ts +137 -0
  264. package/server/service/catalog-resolver.ts +102 -0
  265. package/server/service/chat-message/chat-message-broadcast.integration.test.ts +73 -0
  266. package/server/service/chat-message/chat-message-broadcast.test.ts +40 -0
  267. package/server/service/chat-message/chat-message-broadcast.ts +27 -0
  268. package/server/service/chat-message/chat-message-publish.ts +22 -0
  269. package/server/service/chat-message/chat-message-subscription.ts +41 -0
  270. package/server/service/chat-message/chat-message.ts +169 -0
  271. package/server/service/chat-message/index.ts +12 -0
  272. package/server/service/chat-session/chat-session.ts +130 -0
  273. package/server/service/chat-session/index.ts +5 -0
  274. package/server/service/chat-session-participant/chat-session-participant.ts +110 -0
  275. package/server/service/chat-session-participant/index.ts +6 -0
  276. package/server/service/chat-session-participant/invite-notification.test.ts +30 -0
  277. package/server/service/chat-session-participant/invite-notification.ts +48 -0
  278. package/server/service/chat-session-participant/presence.test.ts +39 -0
  279. package/server/service/chat-session-participant/presence.ts +21 -0
  280. package/server/service/chat-session-resolver.ts +312 -0
  281. package/server/service/component-tree.test.ts +248 -0
  282. package/server/service/component-tree.ts +135 -0
  283. package/server/service/index.ts +32 -0
  284. package/server/service/mention-marker.extract.test.ts +61 -0
  285. package/server/service/mention-marker.test.ts +228 -0
  286. package/server/service/mention-marker.ts +184 -0
  287. package/server/service/mention-resolver.test.ts +273 -0
  288. package/server/service/mention-resolver.ts +264 -0
  289. package/server/service/patch-entry/board-patch-broadcast.ts +24 -0
  290. package/server/service/patch-entry/board-patch-subscription.test.ts +40 -0
  291. package/server/service/patch-entry/board-patch-subscription.ts +50 -0
  292. package/server/service/patch-entry/index.ts +12 -0
  293. package/server/service/patch-entry/patch-entry.ts +89 -0
  294. package/server/service/prefab-registry.test.ts +172 -0
  295. package/server/service/prefab-registry.ts +332 -0
  296. package/server/service/styling/clear-tools.test.ts +164 -0
  297. package/server/service/styling/clear-tools.ts +69 -0
  298. package/server/service/styling/color-utils.test.ts +130 -0
  299. package/server/service/styling/color-utils.ts +226 -0
  300. package/server/service/styling/copy-tools.test.ts +152 -0
  301. package/server/service/styling/copy-tools.ts +115 -0
  302. package/server/service/styling/effect-tools.test.ts +277 -0
  303. package/server/service/styling/effect-tools.ts +124 -0
  304. package/server/service/styling/fill-tools.test.ts +331 -0
  305. package/server/service/styling/fill-tools.ts +293 -0
  306. package/server/service/styling/material-tools.test.ts +294 -0
  307. package/server/service/styling/material-tools.ts +114 -0
  308. package/server/service/styling/read-tools.test.ts +78 -0
  309. package/server/service/styling/read-tools.ts +67 -0
  310. package/server/service/styling/registry.test.ts +203 -0
  311. package/server/service/styling/registry.ts +423 -0
  312. package/server/service/styling/stroke-tools.test.ts +245 -0
  313. package/server/service/styling/stroke-tools.ts +145 -0
  314. package/server/service/styling/text-tools.test.ts +253 -0
  315. package/server/service/styling/text-tools.ts +131 -0
  316. package/server/service/styling/tier1-tools.test.ts +300 -0
  317. package/server/service/subscribe-auth.test.ts +57 -0
  318. package/server/service/subscribe-auth.ts +34 -0
  319. package/server/service/types.ts +235 -0
  320. package/server/service/validation/board-model-schema.test.ts +778 -0
  321. package/server/service/validation/board-model-schema.ts +446 -0
  322. package/server/service/validation/tool-validation.ts +228 -0
  323. package/things-factory.config.js +1 -0
  324. package/translations/en.json +49 -0
  325. package/translations/ja.json +48 -0
  326. package/translations/ko.json +49 -0
  327. package/translations/ms.json +48 -0
  328. package/translations/zh.json +48 -0
  329. package/tsconfig.json +9 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `#token` 멘션 해석 (things-factory 식별자 컨벤션 준수).
3
+ *
4
+ * things-factory 정책:
5
+ * - `#` 은 사용자가 컴포넌트를 가리키는 SELECTOR. things-scene Finder 의
6
+ * CSS 셀렉터 컨셉과 같은 맥락 — 와이드 검색 (name / id / type 셋 다 OR).
7
+ * - **refid 는 시스템 내부 핸들** — 사용자에게 노출 금지. 사용자가 본 적도
8
+ * 없는 숫자라 `(refid 35)` 같은 표기는 혼란만 가중.
9
+ * - id 는 데이터 바인딩 식별자 (human-readable). unique 제약 없음.
10
+ * - name 은 사용자 라벨. 가장 친숙.
11
+ *
12
+ * Resolver 정책:
13
+ * - 본문의 `#token` 은 LITERAL 보존 (refid 본문 노출 0)
14
+ * - 매칭 결과는 본문 끝에 구조화된 "mention map" 블록으로 첨부
15
+ * - AI 는 시스템 프롬프트의 안내에 따라 mention map 의 refid 를 tool
16
+ * 호출 인자로만 사용. 사용자 답변 텍스트에서는 name/id 로 지칭.
17
+ *
18
+ * Token 매칭 (모두 시도, OR):
19
+ * - name 정확 일치 (case-insensitive)
20
+ * - id 정확 일치 (case-insensitive)
21
+ * - type 정확 일치 (case-insensitive) — `#rect` 같이 모든 type 가리키기
22
+ *
23
+ * 부분 / fuzzy 매칭 안 함 — popup 측에서 사용자가 정확한 식별자 선택.
24
+ *
25
+ * Token 정규식: /#([^\s#]+)/g (한글/영문/하이픈/언더스코어/숫자).
26
+ */
27
+ import type { BoardModel } from '@things-factory/board-import';
28
+ /** 매칭된 컴포넌트의 핵심 정보 — refid 는 시스템 핸들, 나머지는 사용자 친화적 식별.
29
+ * things-scene F-Finder 와 동일한 검색 필드 (id/name/type/tag/class/text)
30
+ * + position (AI 가 식별 + 조작 좌표 판단에 활용). */
31
+ export interface MentionedComponent {
32
+ refid: number;
33
+ name?: string;
34
+ id?: string;
35
+ type: string;
36
+ tag?: string;
37
+ class?: string;
38
+ text?: string;
39
+ /** 중심 좌표 (rect: left+w/2, top+h/2 / circle: cx, cy) — 정수 반올림 */
40
+ position?: {
41
+ x: number;
42
+ y: number;
43
+ };
44
+ }
45
+ /** popup 에서 사용자가 명시 선택한 컴포넌트 — server 가 정확한 매칭에 사용. */
46
+ export interface MentionPick {
47
+ /** 사용자 텍스트의 #token 부분 (예: '경광등1') */
48
+ token: string;
49
+ /** popup 에서 선택된 정확한 refid */
50
+ refid: number;
51
+ }
52
+ export interface ResolveMentionsOptions {
53
+ /** popup 에서 명시 선택된 mentions — 해당 token 은 fallback 매칭 안 거치고 정확한 refid 로 즉시 해석 */
54
+ picks?: MentionPick[];
55
+ }
56
+ export interface MentionMatch {
57
+ /** 사용자가 입력한 #token 의 token 부분 */
58
+ token: string;
59
+ /** 토큰 위치 (start: '#' 위치, end: 마지막 글자 다음) */
60
+ start: number;
61
+ end: number;
62
+ /** name/id/type 매칭으로 발견한 컴포넌트들 */
63
+ matched: MentionedComponent[];
64
+ }
65
+ /**
66
+ * 텍스트의 모든 #token 을 보드에서 매칭. UI / 디버그 / 테스트용.
67
+ */
68
+ export declare function findMentions(text: string, currentBoard: BoardModel | null | undefined, options?: ResolveMentionsOptions): MentionMatch[];
69
+ /**
70
+ * Token 으로 컴포넌트 매칭 — name/id/type/tag/class/text OR (things-scene F-Finder
71
+ * 동일 필드 셋). 여러 기준에서 동시 일치하더라도 같은 refid 는 한 번만 반환 (dedup).
72
+ *
73
+ * picks 에 해당 token 이 있으면 fallback 안 거치고 그 refid 로 직접 매칭 (popup 에서
74
+ * 사용자가 명시 선택한 결정적 매핑). 그 refid 가 보드에 없으면 fallback 으로.
75
+ */
76
+ export declare function lookupComponents(token: string, components: readonly any[], picks?: readonly MentionPick[]): MentionedComponent[];
77
+ /**
78
+ * 텍스트의 #token 을 LLM 친화 형태로 변환.
79
+ *
80
+ * 본문은 그대로 + 매칭 성공한 토큰의 mention map 을 끝에 첨부:
81
+ *
82
+ * "[원본 본문]
83
+ *
84
+ * --- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---
85
+ * #경광등1 →
86
+ * { name='경광등1', id='motor-1', type='rect', refid=35 }
87
+ * #펌프 →
88
+ * { name='펌프A', id='pump-1', type='rect', refid=42 }
89
+ * { name='펌프B', id='pump-2', type='rect', refid=51 }"
90
+ *
91
+ * AI 는 system prompt 안내에 따라 refid 를 tool 호출 인자로만 사용. 답변
92
+ * 텍스트에서는 refid 직접 언급 금지.
93
+ *
94
+ * 매칭 0 → 변경 없음 (본문 그대로 반환).
95
+ */
96
+ export declare function resolveMentions(text: string, currentBoard: BoardModel | null | undefined, options?: ResolveMentionsOptions): string;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findMentions = findMentions;
4
+ exports.lookupComponents = lookupComponents;
5
+ exports.resolveMentions = resolveMentions;
6
+ const MENTION_PATTERN = /#([^\s#]+)/g;
7
+ /**
8
+ * 텍스트의 모든 #token 을 보드에서 매칭. UI / 디버그 / 테스트용.
9
+ */
10
+ function findMentions(text, currentBoard, options = {}) {
11
+ if (!text || typeof text !== 'string')
12
+ return [];
13
+ const components = currentBoard?.components ?? [];
14
+ return collectMatches(text, components, options.picks);
15
+ }
16
+ /**
17
+ * Token 으로 컴포넌트 매칭 — name/id/type/tag/class/text OR (things-scene F-Finder
18
+ * 동일 필드 셋). 여러 기준에서 동시 일치하더라도 같은 refid 는 한 번만 반환 (dedup).
19
+ *
20
+ * picks 에 해당 token 이 있으면 fallback 안 거치고 그 refid 로 직접 매칭 (popup 에서
21
+ * 사용자가 명시 선택한 결정적 매핑). 그 refid 가 보드에 없으면 fallback 으로.
22
+ */
23
+ function lookupComponents(token, components, picks) {
24
+ if (!token || components.length === 0)
25
+ return [];
26
+ // picks 우선 — popup 으로 명시 선택된 token 은 그 refid 로 직접 매칭
27
+ if (picks) {
28
+ const pick = picks.find(p => p.token === token);
29
+ if (pick) {
30
+ const c = components.find((x) => x && typeof x.refid === 'number' && x.refid === pick.refid);
31
+ if (c) {
32
+ const fields = readSearchFields(c);
33
+ return [
34
+ {
35
+ refid: pick.refid,
36
+ name: fields.name,
37
+ id: fields.id,
38
+ type: fields.type,
39
+ tag: fields.tag,
40
+ class: fields.cls,
41
+ text: fields.text,
42
+ position: extractPosition(c)
43
+ }
44
+ ];
45
+ }
46
+ // pick 이 stale (refid 가 더 이상 보드에 없음) — fallback 으로 진행
47
+ }
48
+ }
49
+ const lower = token.toLowerCase();
50
+ const out = [];
51
+ const seen = new Set();
52
+ for (const c of components) {
53
+ if (!c || typeof c.refid !== 'number')
54
+ continue;
55
+ const refid = c.refid;
56
+ if (seen.has(refid))
57
+ continue;
58
+ const fields = readSearchFields(c);
59
+ if ((fields.name && fields.name.toLowerCase() === lower) ||
60
+ (fields.id && fields.id.toLowerCase() === lower) ||
61
+ fields.type.toLowerCase() === lower ||
62
+ (fields.tag && fields.tag.toLowerCase() === lower) ||
63
+ (fields.cls && fields.cls.toLowerCase() === lower) ||
64
+ (fields.text && fields.text.toLowerCase() === lower)) {
65
+ seen.add(refid);
66
+ out.push({
67
+ refid,
68
+ name: fields.name,
69
+ id: fields.id,
70
+ type: fields.type,
71
+ tag: fields.tag,
72
+ class: fields.cls,
73
+ text: fields.text,
74
+ position: extractPosition(c)
75
+ });
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+ /** 컴포넌트 중심 좌표 — rect: left+w/2 / circle: cx,cy. */
81
+ function extractPosition(c) {
82
+ if (typeof c.cx === 'number' && typeof c.cy === 'number') {
83
+ return { x: Math.round(c.cx), y: Math.round(c.cy) };
84
+ }
85
+ if (typeof c.left === 'number' && typeof c.top === 'number') {
86
+ const w = typeof c.width === 'number' ? c.width : 0;
87
+ const h = typeof c.height === 'number' ? c.height : 0;
88
+ return { x: Math.round(c.left + w / 2), y: Math.round(c.top + h / 2) };
89
+ }
90
+ return undefined;
91
+ }
92
+ function readSearchFields(c) {
93
+ const str = (v) => typeof v === 'string' && v.length > 0 ? v : undefined;
94
+ return {
95
+ name: str(c.name),
96
+ id: str(c.id),
97
+ type: typeof c.type === 'string' ? c.type : 'unknown',
98
+ tag: str(c.tag),
99
+ cls: str(c.class),
100
+ text: str(c.text)
101
+ };
102
+ }
103
+ /**
104
+ * 텍스트의 #token 을 LLM 친화 형태로 변환.
105
+ *
106
+ * 본문은 그대로 + 매칭 성공한 토큰의 mention map 을 끝에 첨부:
107
+ *
108
+ * "[원본 본문]
109
+ *
110
+ * --- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---
111
+ * #경광등1 →
112
+ * { name='경광등1', id='motor-1', type='rect', refid=35 }
113
+ * #펌프 →
114
+ * { name='펌프A', id='pump-1', type='rect', refid=42 }
115
+ * { name='펌프B', id='pump-2', type='rect', refid=51 }"
116
+ *
117
+ * AI 는 system prompt 안내에 따라 refid 를 tool 호출 인자로만 사용. 답변
118
+ * 텍스트에서는 refid 직접 언급 금지.
119
+ *
120
+ * 매칭 0 → 변경 없음 (본문 그대로 반환).
121
+ */
122
+ function resolveMentions(text, currentBoard, options = {}) {
123
+ if (!text || typeof text !== 'string')
124
+ return text;
125
+ const components = currentBoard?.components ?? [];
126
+ if (components.length === 0)
127
+ return text;
128
+ const matches = collectMatches(text, components, options.picks);
129
+ const resolved = matches.filter(m => m.matched.length > 0);
130
+ if (resolved.length === 0)
131
+ return text;
132
+ const block = [
133
+ '',
134
+ '--- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---'
135
+ ];
136
+ for (const m of resolved) {
137
+ block.push(`#${m.token} →`);
138
+ for (const c of m.matched) {
139
+ block.push(` ${formatComponentRef(c)}`);
140
+ }
141
+ }
142
+ return text + '\n' + block.join('\n');
143
+ }
144
+ function collectMatches(text, components, picks) {
145
+ const out = [];
146
+ const pattern = new RegExp(MENTION_PATTERN.source, 'g');
147
+ let m;
148
+ while ((m = pattern.exec(text)) !== null) {
149
+ const token = m[1];
150
+ out.push({
151
+ token,
152
+ start: m.index,
153
+ end: m.index + m[0].length,
154
+ matched: lookupComponents(token, components, picks)
155
+ });
156
+ }
157
+ return out;
158
+ }
159
+ function formatComponentRef(c) {
160
+ const parts = [];
161
+ if (c.name)
162
+ parts.push(`name='${c.name}'`);
163
+ if (c.id)
164
+ parts.push(`id='${c.id}'`);
165
+ parts.push(`type='${c.type}'`);
166
+ if (c.tag)
167
+ parts.push(`tag='${c.tag}'`);
168
+ if (c.class)
169
+ parts.push(`class='${c.class}'`);
170
+ if (c.text)
171
+ parts.push(`text='${c.text}'`);
172
+ if (c.position)
173
+ parts.push(`pos=(${c.position.x},${c.position.y})`);
174
+ parts.push(`refid=${c.refid}`);
175
+ return `{ ${parts.join(', ')} }`;
176
+ }
177
+ //# sourceMappingURL=mention-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mention-resolver.js","sourceRoot":"","sources":["../../server/service/mention-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `#token` 멘션 해석 (things-factory 식별자 컨벤션 준수).\n *\n * things-factory 정책:\n * - `#` 은 사용자가 컴포넌트를 가리키는 SELECTOR. things-scene Finder 의\n * CSS 셀렉터 컨셉과 같은 맥락 — 와이드 검색 (name / id / type 셋 다 OR).\n * - **refid 는 시스템 내부 핸들** — 사용자에게 노출 금지. 사용자가 본 적도\n * 없는 숫자라 `(refid 35)` 같은 표기는 혼란만 가중.\n * - id 는 데이터 바인딩 식별자 (human-readable). unique 제약 없음.\n * - name 은 사용자 라벨. 가장 친숙.\n *\n * Resolver 정책:\n * - 본문의 `#token` 은 LITERAL 보존 (refid 본문 노출 0)\n * - 매칭 결과는 본문 끝에 구조화된 \"mention map\" 블록으로 첨부\n * - AI 는 시스템 프롬프트의 안내에 따라 mention map 의 refid 를 tool\n * 호출 인자로만 사용. 사용자 답변 텍스트에서는 name/id 로 지칭.\n *\n * Token 매칭 (모두 시도, OR):\n * - name 정확 일치 (case-insensitive)\n * - id 정확 일치 (case-insensitive)\n * - type 정확 일치 (case-insensitive) — `#rect` 같이 모든 type 가리키기\n *\n * 부분 / fuzzy 매칭 안 함 — popup 측에서 사용자가 정확한 식별자 선택.\n *\n * Token 정규식: /#([^\\s#]+)/g (한글/영문/하이픈/언더스코어/숫자).\n */\nimport type { BoardModel } from '@things-factory/board-import'\n\nconst MENTION_PATTERN = /#([^\\s#]+)/g\n\n/** 매칭된 컴포넌트의 핵심 정보 — refid 는 시스템 핸들, 나머지는 사용자 친화적 식별.\n * things-scene F-Finder 와 동일한 검색 필드 (id/name/type/tag/class/text)\n * + position (AI 가 식별 + 조작 좌표 판단에 활용). */\nexport interface MentionedComponent {\n refid: number\n name?: string\n id?: string\n type: string\n tag?: string\n class?: string\n text?: string\n /** 중심 좌표 (rect: left+w/2, top+h/2 / circle: cx, cy) — 정수 반올림 */\n position?: { x: number; y: number }\n}\n\n/** popup 에서 사용자가 명시 선택한 컴포넌트 — server 가 정확한 매칭에 사용. */\nexport interface MentionPick {\n /** 사용자 텍스트의 #token 부분 (예: '경광등1') */\n token: string\n /** popup 에서 선택된 정확한 refid */\n refid: number\n}\n\nexport interface ResolveMentionsOptions {\n /** popup 에서 명시 선택된 mentions — 해당 token 은 fallback 매칭 안 거치고 정확한 refid 로 즉시 해석 */\n picks?: MentionPick[]\n}\n\nexport interface MentionMatch {\n /** 사용자가 입력한 #token 의 token 부분 */\n token: string\n /** 토큰 위치 (start: '#' 위치, end: 마지막 글자 다음) */\n start: number\n end: number\n /** name/id/type 매칭으로 발견한 컴포넌트들 */\n matched: MentionedComponent[]\n}\n\n/**\n * 텍스트의 모든 #token 을 보드에서 매칭. UI / 디버그 / 테스트용.\n */\nexport function findMentions(\n text: string,\n currentBoard: BoardModel | null | undefined,\n options: ResolveMentionsOptions = {}\n): MentionMatch[] {\n if (!text || typeof text !== 'string') return []\n const components = currentBoard?.components ?? []\n return collectMatches(text, components, options.picks)\n}\n\n\n/**\n * Token 으로 컴포넌트 매칭 — name/id/type/tag/class/text OR (things-scene F-Finder\n * 동일 필드 셋). 여러 기준에서 동시 일치하더라도 같은 refid 는 한 번만 반환 (dedup).\n *\n * picks 에 해당 token 이 있으면 fallback 안 거치고 그 refid 로 직접 매칭 (popup 에서\n * 사용자가 명시 선택한 결정적 매핑). 그 refid 가 보드에 없으면 fallback 으로.\n */\nexport function lookupComponents(\n token: string,\n components: readonly any[],\n picks?: readonly MentionPick[]\n): MentionedComponent[] {\n if (!token || components.length === 0) return []\n\n // picks 우선 — popup 으로 명시 선택된 token 은 그 refid 로 직접 매칭\n if (picks) {\n const pick = picks.find(p => p.token === token)\n if (pick) {\n const c = components.find(\n (x: any) => x && typeof x.refid === 'number' && x.refid === pick.refid\n )\n if (c) {\n const fields = readSearchFields(c)\n return [\n {\n refid: pick.refid,\n name: fields.name,\n id: fields.id,\n type: fields.type,\n tag: fields.tag,\n class: fields.cls,\n text: fields.text,\n position: extractPosition(c)\n }\n ]\n }\n // pick 이 stale (refid 가 더 이상 보드에 없음) — fallback 으로 진행\n }\n }\n\n const lower = token.toLowerCase()\n const out: MentionedComponent[] = []\n const seen = new Set<number>()\n for (const c of components) {\n if (!c || typeof (c as any).refid !== 'number') continue\n const refid = (c as any).refid\n if (seen.has(refid)) continue\n const fields = readSearchFields(c)\n if (\n (fields.name && fields.name.toLowerCase() === lower) ||\n (fields.id && fields.id.toLowerCase() === lower) ||\n fields.type.toLowerCase() === lower ||\n (fields.tag && fields.tag.toLowerCase() === lower) ||\n (fields.cls && fields.cls.toLowerCase() === lower) ||\n (fields.text && fields.text.toLowerCase() === lower)\n ) {\n seen.add(refid)\n out.push({\n refid,\n name: fields.name,\n id: fields.id,\n type: fields.type,\n tag: fields.tag,\n class: fields.cls,\n text: fields.text,\n position: extractPosition(c)\n })\n }\n }\n return out\n}\n\n/** 컴포넌트 중심 좌표 — rect: left+w/2 / circle: cx,cy. */\nfunction extractPosition(c: any): { x: number; y: number } | undefined {\n if (typeof c.cx === 'number' && typeof c.cy === 'number') {\n return { x: Math.round(c.cx), y: Math.round(c.cy) }\n }\n if (typeof c.left === 'number' && typeof c.top === 'number') {\n const w = typeof c.width === 'number' ? c.width : 0\n const h = typeof c.height === 'number' ? c.height : 0\n return { x: Math.round(c.left + w / 2), y: Math.round(c.top + h / 2) }\n }\n return undefined\n}\n\nfunction readSearchFields(c: any): {\n name?: string\n id?: string\n type: string\n tag?: string\n cls?: string\n text?: string\n} {\n const str = (v: any): string | undefined =>\n typeof v === 'string' && v.length > 0 ? v : undefined\n return {\n name: str(c.name),\n id: str(c.id),\n type: typeof c.type === 'string' ? c.type : 'unknown',\n tag: str(c.tag),\n cls: str(c.class),\n text: str(c.text)\n }\n}\n\n/**\n * 텍스트의 #token 을 LLM 친화 형태로 변환.\n *\n * 본문은 그대로 + 매칭 성공한 토큰의 mention map 을 끝에 첨부:\n *\n * \"[원본 본문]\n *\n * --- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---\n * #경광등1 →\n * { name='경광등1', id='motor-1', type='rect', refid=35 }\n * #펌프 →\n * { name='펌프A', id='pump-1', type='rect', refid=42 }\n * { name='펌프B', id='pump-2', type='rect', refid=51 }\"\n *\n * AI 는 system prompt 안내에 따라 refid 를 tool 호출 인자로만 사용. 답변\n * 텍스트에서는 refid 직접 언급 금지.\n *\n * 매칭 0 → 변경 없음 (본문 그대로 반환).\n */\nexport function resolveMentions(\n text: string,\n currentBoard: BoardModel | null | undefined,\n options: ResolveMentionsOptions = {}\n): string {\n if (!text || typeof text !== 'string') return text\n const components = currentBoard?.components ?? []\n if (components.length === 0) return text\n\n const matches = collectMatches(text, components, options.picks)\n const resolved = matches.filter(m => m.matched.length > 0)\n if (resolved.length === 0) return text\n\n const block: string[] = [\n '',\n '--- mention map (자동 첨부 — 사용자가 # 로 지칭한 컴포넌트) ---'\n ]\n for (const m of resolved) {\n block.push(`#${m.token} →`)\n for (const c of m.matched) {\n block.push(` ${formatComponentRef(c)}`)\n }\n }\n return text + '\\n' + block.join('\\n')\n}\n\nfunction collectMatches(\n text: string,\n components: readonly any[],\n picks?: readonly MentionPick[]\n): MentionMatch[] {\n const out: MentionMatch[] = []\n const pattern = new RegExp(MENTION_PATTERN.source, 'g')\n let m: RegExpExecArray | null\n while ((m = pattern.exec(text)) !== null) {\n const token = m[1]\n out.push({\n token,\n start: m.index,\n end: m.index + m[0].length,\n matched: lookupComponents(token, components, picks)\n })\n }\n return out\n}\n\nfunction formatComponentRef(c: MentionedComponent): string {\n const parts: string[] = []\n if (c.name) parts.push(`name='${c.name}'`)\n if (c.id) parts.push(`id='${c.id}'`)\n parts.push(`type='${c.type}'`)\n if (c.tag) parts.push(`tag='${c.tag}'`)\n if (c.class) parts.push(`class='${c.class}'`)\n if (c.text) parts.push(`text='${c.text}'`)\n if (c.position) parts.push(`pos=(${c.position.x},${c.position.y})`)\n parts.push(`refid=${c.refid}`)\n return `{ ${parts.join(', ')} }`\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import type { PatchEntry } from './patch-entry.js';
2
+ /** board-patch 브로드캐스트 토픽. */
3
+ export declare const BOARD_PATCH_TOPIC = "board-patch";
4
+ /** 방송 페이로드 — 구독 필터(domain+board)용 메타 + PatchEntry 본체. */
5
+ export interface BoardPatchPayload {
6
+ domainId: string;
7
+ boardId?: string;
8
+ patch: PatchEntry;
9
+ }
10
+ /**
11
+ * 같은 domain + board 구독자에게만 통과시키는 필터 (도메인 격리 = 보안 핵심).
12
+ */
13
+ export declare function boardPatchFilter(domainId: string, boardId: string): (payload: BoardPatchPayload) => boolean;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BOARD_PATCH_TOPIC = void 0;
4
+ exports.boardPatchFilter = boardPatchFilter;
5
+ /** board-patch 브로드캐스트 토픽. */
6
+ exports.BOARD_PATCH_TOPIC = 'board-patch';
7
+ /**
8
+ * 같은 domain + board 구독자에게만 통과시키는 필터 (도메인 격리 = 보안 핵심).
9
+ */
10
+ function boardPatchFilter(domainId, boardId) {
11
+ return (payload) => !!payload && payload.domainId === domainId && payload.boardId === boardId;
12
+ }
13
+ //# sourceMappingURL=board-patch-broadcast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-patch-broadcast.js","sourceRoot":"","sources":["../../../server/service/patch-entry/board-patch-broadcast.ts"],"names":[],"mappings":";;;AAoBA,4CAGC;AAhBD,6BAA6B;AAChB,QAAA,iBAAiB,GAAG,aAAa,CAAA;AAS9C;;GAEG;AACH,SAAgB,gBAAgB,CAAC,QAAgB,EAAE,OAAe;IAChE,OAAO,CAAC,OAA0B,EAAW,EAAE,CAC7C,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,KAAK,OAAO,CAAA;AAC7E,CAAC","sourcesContent":["/*\n * board-patch 브로드캐스트 — 순수 로직 (토픽/페이로드/필터). 데코레이터·pubsub import 없음\n * → 단위테스트 가능. 데코레이터 resolver(board-patch-subscription.ts)와 pubsub 의존\n * publishBoardPatch 는 별도.\n */\nimport type { PatchEntry } from './patch-entry.js' // type-only — 런타임 import 아님\n\n/** board-patch 브로드캐스트 토픽. */\nexport const BOARD_PATCH_TOPIC = 'board-patch'\n\n/** 방송 페이로드 — 구독 필터(domain+board)용 메타 + PatchEntry 본체. */\nexport interface BoardPatchPayload {\n domainId: string\n boardId?: string\n patch: PatchEntry\n}\n\n/**\n * 같은 domain + board 구독자에게만 통과시키는 필터 (도메인 격리 = 보안 핵심).\n */\nexport function boardPatchFilter(domainId: string, boardId: string) {\n return (payload: BoardPatchPayload): boolean =>\n !!payload && payload.domainId === domainId && payload.boardId === boardId\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import { PatchEntry } from './patch-entry.js';
2
+ import { type BoardPatchPayload } from './board-patch-broadcast.js';
3
+ export { BOARD_PATCH_TOPIC, boardPatchFilter } from './board-patch-broadcast.js';
4
+ export type { BoardPatchPayload } from './board-patch-broadcast.js';
5
+ /**
6
+ * PatchEntry 생성 직후 호출 — 같은 board 구독자에게 방송.
7
+ * boardId 없으면(세션 미연결) skip. pubsub 미가용/실패해도 본 흐름(영속 완료)을 막지 않는다.
8
+ */
9
+ export declare function publishBoardPatch(payload: BoardPatchPayload): void;
10
+ export declare class BoardPatchSubscription {
11
+ boardPatchActivity(payload: BoardPatchPayload, boardId: string): PatchEntry;
12
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BoardPatchSubscription = exports.boardPatchFilter = exports.BOARD_PATCH_TOPIC = void 0;
4
+ exports.publishBoardPatch = publishBoardPatch;
5
+ const tslib_1 = require("tslib");
6
+ /*
7
+ * BoardPatchSubscription (Phase Board-AI C1 — op-브로드캐스트 spine).
8
+ *
9
+ * PatchEntry(AI 또는 user-direct 편집) 가 생성될 때마다 같은 board 구독자에게 방송 →
10
+ * 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를 applyBoardEditPatch 로 적용.
11
+ *
12
+ * 전송/패턴은 기존 board-service/board-subscription.ts 복제 (PubSub + graphql-ws, domain 격리).
13
+ * 순수 로직(토픽/필터)은 board-patch-broadcast.ts (테스트 가능). 여기엔 pubsub 의존만.
14
+ */
15
+ const type_graphql_1 = require("type-graphql");
16
+ const graphql_yoga_1 = require("graphql-yoga");
17
+ const shell_1 = require("@things-factory/shell");
18
+ const patch_entry_js_1 = require("./patch-entry.js");
19
+ const board_patch_broadcast_js_1 = require("./board-patch-broadcast.js");
20
+ const subscribe_auth_js_1 = require("../subscribe-auth.js");
21
+ var board_patch_broadcast_js_2 = require("./board-patch-broadcast.js");
22
+ Object.defineProperty(exports, "BOARD_PATCH_TOPIC", { enumerable: true, get: function () { return board_patch_broadcast_js_2.BOARD_PATCH_TOPIC; } });
23
+ Object.defineProperty(exports, "boardPatchFilter", { enumerable: true, get: function () { return board_patch_broadcast_js_2.boardPatchFilter; } });
24
+ /**
25
+ * PatchEntry 생성 직후 호출 — 같은 board 구독자에게 방송.
26
+ * boardId 없으면(세션 미연결) skip. pubsub 미가용/실패해도 본 흐름(영속 완료)을 막지 않는다.
27
+ */
28
+ function publishBoardPatch(payload) {
29
+ if (!payload.boardId)
30
+ return;
31
+ try {
32
+ shell_1.pubsub.publish(board_patch_broadcast_js_1.BOARD_PATCH_TOPIC, payload);
33
+ }
34
+ catch {
35
+ /* 방송 생략 — 영속은 이미 끝남 */
36
+ }
37
+ }
38
+ let BoardPatchSubscription = class BoardPatchSubscription {
39
+ boardPatchActivity(payload, boardId) {
40
+ return payload.patch;
41
+ }
42
+ };
43
+ exports.BoardPatchSubscription = BoardPatchSubscription;
44
+ tslib_1.__decorate([
45
+ (0, type_graphql_1.Subscription)({
46
+ description: 'Subscribes to board edit patches (AI or user-direct) for real-time collaboration on a board.',
47
+ subscribe: async ({ args, context }) => {
48
+ const { domain, user } = context.state;
49
+ const { boardId } = args;
50
+ await (0, subscribe_auth_js_1.assertDomainSubscribeAllowed)(domain, user, (d, u) => process.superUserGranted(d, u));
51
+ return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe(board_patch_broadcast_js_1.BOARD_PATCH_TOPIC), (0, graphql_yoga_1.filter)((0, board_patch_broadcast_js_1.boardPatchFilter)(domain.id, boardId)));
52
+ }
53
+ }),
54
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
55
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('boardId')),
56
+ tslib_1.__metadata("design:type", Function),
57
+ tslib_1.__metadata("design:paramtypes", [Object, String]),
58
+ tslib_1.__metadata("design:returntype", patch_entry_js_1.PatchEntry)
59
+ ], BoardPatchSubscription.prototype, "boardPatchActivity", null);
60
+ exports.BoardPatchSubscription = BoardPatchSubscription = tslib_1.__decorate([
61
+ (0, type_graphql_1.Resolver)(patch_entry_js_1.PatchEntry)
62
+ ], BoardPatchSubscription);
63
+ //# sourceMappingURL=board-patch-subscription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-patch-subscription.js","sourceRoot":"","sources":["../../../server/service/patch-entry/board-patch-subscription.ts"],"names":[],"mappings":";;;AAuBA,8CAOC;;AA9BD;;;;;;;;GAQG;AACH,+CAAgE;AAChE,+CAA2C;AAC3C,iDAA8C;AAC9C,qDAA6C;AAC7C,yEAAwG;AACxG,4DAAmE;AAEnE,uEAAgF;AAAvE,6HAAA,iBAAiB,OAAA;AAAE,4HAAA,gBAAgB,OAAA;AAG5C;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,OAA0B;IAC1D,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAM;IAC5B,IAAI,CAAC;QACH,cAAM,CAAC,OAAO,CAAC,4CAAiB,EAAE,OAAO,CAAC,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,uBAAuB;IACzB,CAAC;AACH,CAAC;AAGM,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAajC,kBAAkB,CAAS,OAA0B,EAAkB,OAAe;QACpF,OAAO,OAAO,CAAC,KAAK,CAAA;IACtB,CAAC;CACF,CAAA;AAhBY,wDAAsB;AAajC;IAZC,IAAA,2BAAY,EAAC;QACZ,WAAW,EACT,8FAA8F;QAChG,SAAS,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;YACrC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;YACtC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;YAExB,MAAM,IAAA,gDAA4B,EAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YAE1F,OAAO,IAAA,mBAAI,EAAC,cAAM,CAAC,SAAS,CAAC,4CAAiB,CAAC,EAAE,IAAA,qBAAM,EAAC,IAAA,2CAAgB,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;QAChG,CAAC;KACF,CAAC;IACkB,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAA8B,mBAAA,IAAA,kBAAG,EAAC,SAAS,CAAC,CAAA;;;4CAAmB,2BAAU;gEAElG;iCAfU,sBAAsB;IADlC,IAAA,uBAAQ,EAAC,2BAAU,CAAC;GACR,sBAAsB,CAgBlC","sourcesContent":["/*\n * BoardPatchSubscription (Phase Board-AI C1 — op-브로드캐스트 spine).\n *\n * PatchEntry(AI 또는 user-direct 편집) 가 생성될 때마다 같은 board 구독자에게 방송 →\n * 실시간 공동편집의 척추. 클라이언트(C2)가 구독해 원격 patch 를 applyBoardEditPatch 로 적용.\n *\n * 전송/패턴은 기존 board-service/board-subscription.ts 복제 (PubSub + graphql-ws, domain 격리).\n * 순수 로직(토픽/필터)은 board-patch-broadcast.ts (테스트 가능). 여기엔 pubsub 의존만.\n */\nimport { Resolver, Subscription, Root, Arg } from 'type-graphql'\nimport { filter, pipe } from 'graphql-yoga'\nimport { pubsub } from '@things-factory/shell'\nimport { PatchEntry } from './patch-entry.js'\nimport { BOARD_PATCH_TOPIC, boardPatchFilter, type BoardPatchPayload } from './board-patch-broadcast.js'\nimport { assertDomainSubscribeAllowed } from '../subscribe-auth.js'\n\nexport { BOARD_PATCH_TOPIC, boardPatchFilter } from './board-patch-broadcast.js'\nexport type { BoardPatchPayload } from './board-patch-broadcast.js'\n\n/**\n * PatchEntry 생성 직후 호출 — 같은 board 구독자에게 방송.\n * boardId 없으면(세션 미연결) skip. pubsub 미가용/실패해도 본 흐름(영속 완료)을 막지 않는다.\n */\nexport function publishBoardPatch(payload: BoardPatchPayload): void {\n if (!payload.boardId) return\n try {\n pubsub.publish(BOARD_PATCH_TOPIC, payload)\n } catch {\n /* 방송 생략 — 영속은 이미 끝남 */\n }\n}\n\n@Resolver(PatchEntry)\nexport class BoardPatchSubscription {\n @Subscription({\n description:\n 'Subscribes to board edit patches (AI or user-direct) for real-time collaboration on a board.',\n subscribe: async ({ args, context }) => {\n const { domain, user } = context.state\n const { boardId } = args\n\n await assertDomainSubscribeAllowed(domain, user, (d, u) => process.superUserGranted(d, u))\n\n return pipe(pubsub.subscribe(BOARD_PATCH_TOPIC), filter(boardPatchFilter(domain.id, boardId)))\n }\n })\n boardPatchActivity(@Root() payload: BoardPatchPayload, @Arg('boardId') boardId: string): PatchEntry {\n return payload.patch\n }\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import { PatchEntry } from './patch-entry.js';
2
+ export { PatchEntry };
3
+ export { BoardPatchSubscription, publishBoardPatch, boardPatchFilter, BOARD_PATCH_TOPIC, type BoardPatchPayload } from './board-patch-subscription.js';
4
+ export declare const entities: (typeof PatchEntry)[];
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.entities = exports.BOARD_PATCH_TOPIC = exports.boardPatchFilter = exports.publishBoardPatch = exports.BoardPatchSubscription = exports.PatchEntry = void 0;
4
+ const patch_entry_js_1 = require("./patch-entry.js");
5
+ Object.defineProperty(exports, "PatchEntry", { enumerable: true, get: function () { return patch_entry_js_1.PatchEntry; } });
6
+ var board_patch_subscription_js_1 = require("./board-patch-subscription.js");
7
+ Object.defineProperty(exports, "BoardPatchSubscription", { enumerable: true, get: function () { return board_patch_subscription_js_1.BoardPatchSubscription; } });
8
+ Object.defineProperty(exports, "publishBoardPatch", { enumerable: true, get: function () { return board_patch_subscription_js_1.publishBoardPatch; } });
9
+ Object.defineProperty(exports, "boardPatchFilter", { enumerable: true, get: function () { return board_patch_subscription_js_1.boardPatchFilter; } });
10
+ Object.defineProperty(exports, "BOARD_PATCH_TOPIC", { enumerable: true, get: function () { return board_patch_subscription_js_1.BOARD_PATCH_TOPIC; } });
11
+ exports.entities = [patch_entry_js_1.PatchEntry];
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/patch-entry/index.ts"],"names":[],"mappings":";;;AAAA,qDAA6C;AAEpC,2FAFA,2BAAU,OAEA;AACnB,6EAMsC;AALpC,qIAAA,sBAAsB,OAAA;AACtB,gIAAA,iBAAiB,OAAA;AACjB,+HAAA,gBAAgB,OAAA;AAChB,gIAAA,iBAAiB,OAAA;AAIN,QAAA,QAAQ,GAAG,CAAC,2BAAU,CAAC,CAAA","sourcesContent":["import { PatchEntry } from './patch-entry.js'\n\nexport { PatchEntry }\nexport {\n BoardPatchSubscription,\n publishBoardPatch,\n boardPatchFilter,\n BOARD_PATCH_TOPIC,\n type BoardPatchPayload\n} from './board-patch-subscription.js'\n\nexport const entities = [PatchEntry]\n"]}
@@ -0,0 +1,16 @@
1
+ import { ChatSession } from '../chat-session/chat-session.js';
2
+ export declare class PatchEntry {
3
+ readonly id?: string;
4
+ session?: ChatSession;
5
+ sessionId?: string;
6
+ /** 'ai' | 'user-direct' | 'import' */
7
+ source: string;
8
+ /** BoardEditOp[] JSON-stringified — column 으로는 long text. */
9
+ ops: string;
10
+ /** GraphQL 노출용 — JSON 객체로 변환. */
11
+ get opsJson(): any;
12
+ summary?: string;
13
+ confidence?: number;
14
+ reverted: boolean;
15
+ createdAt?: Date;
16
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PatchEntry = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /**
6
+ * PatchEntry — 보드의 한 변경 단위 (영구 이력).
7
+ *
8
+ * source 별:
9
+ * - 'ai': AI 가 chat 응답에서 생성
10
+ * - 'user-direct': 사용자가 모델러에서 직접 편집 — 변경 이벤트로 자동 기록
11
+ * - 'import': ImportSession 결과 (도면 → 보드)
12
+ *
13
+ * AI 컨텍스트 유지의 핵심: 사용자 직접 편집도 PatchEntry 로 기록되어
14
+ * 다음 chat 호출 시 system 메시지로 자동 합류 → AI 가 모든 변경을 인지.
15
+ */
16
+ const typeorm_1 = require("typeorm");
17
+ const type_graphql_1 = require("type-graphql");
18
+ const graphql_scalars_1 = require("graphql-scalars");
19
+ const env_1 = require("@things-factory/env");
20
+ const chat_session_js_1 = require("../chat-session/chat-session.js");
21
+ const ORMCONFIG = env_1.config.get('ormconfig', {});
22
+ const DATABASE_TYPE = ORMCONFIG.type;
23
+ let PatchEntry = class PatchEntry {
24
+ /** GraphQL 노출용 — JSON 객체로 변환. */
25
+ get opsJson() {
26
+ try {
27
+ return JSON.parse(this.ops || '[]');
28
+ }
29
+ catch {
30
+ return [];
31
+ }
32
+ }
33
+ };
34
+ exports.PatchEntry = PatchEntry;
35
+ tslib_1.__decorate([
36
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
37
+ (0, type_graphql_1.Field)(type => type_graphql_1.ID, { nullable: true }),
38
+ tslib_1.__metadata("design:type", String)
39
+ ], PatchEntry.prototype, "id", void 0);
40
+ tslib_1.__decorate([
41
+ (0, typeorm_1.ManyToOne)(type => chat_session_js_1.ChatSession, session => session.patches, { onDelete: 'CASCADE' }),
42
+ tslib_1.__metadata("design:type", chat_session_js_1.ChatSession)
43
+ ], PatchEntry.prototype, "session", void 0);
44
+ tslib_1.__decorate([
45
+ (0, typeorm_1.RelationId)((patch) => patch.session),
46
+ tslib_1.__metadata("design:type", String)
47
+ ], PatchEntry.prototype, "sessionId", void 0);
48
+ tslib_1.__decorate([
49
+ (0, typeorm_1.Column)({ type: 'varchar', length: 32 }),
50
+ (0, type_graphql_1.Field)({ description: "'ai' | 'user-direct' | 'import'" }),
51
+ tslib_1.__metadata("design:type", String)
52
+ ], PatchEntry.prototype, "source", void 0);
53
+ tslib_1.__decorate([
54
+ (0, typeorm_1.Column)({
55
+ type: DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
56
+ ? 'longtext'
57
+ : DATABASE_TYPE == 'oracle'
58
+ ? 'clob'
59
+ : DATABASE_TYPE == 'mssql'
60
+ ? 'nvarchar'
61
+ : 'text',
62
+ length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
63
+ }),
64
+ tslib_1.__metadata("design:type", String)
65
+ ], PatchEntry.prototype, "ops", void 0);
66
+ tslib_1.__decorate([
67
+ (0, type_graphql_1.Field)(type => graphql_scalars_1.GraphQLJSON, { description: 'BoardEditOp[] (parsed JSON array).' }),
68
+ tslib_1.__metadata("design:type", Object),
69
+ tslib_1.__metadata("design:paramtypes", [])
70
+ ], PatchEntry.prototype, "opsJson", null);
71
+ tslib_1.__decorate([
72
+ (0, typeorm_1.Column)({ type: 'text', nullable: true }),
73
+ (0, type_graphql_1.Field)({ nullable: true, description: 'Short human summary of this patch.' }),
74
+ tslib_1.__metadata("design:type", String)
75
+ ], PatchEntry.prototype, "summary", void 0);
76
+ tslib_1.__decorate([
77
+ (0, typeorm_1.Column)({ type: 'float', nullable: true }),
78
+ (0, type_graphql_1.Field)(type => type_graphql_1.Float, { nullable: true, description: 'AI confidence 0..1 (null for user-direct).' }),
79
+ tslib_1.__metadata("design:type", Number)
80
+ ], PatchEntry.prototype, "confidence", void 0);
81
+ tslib_1.__decorate([
82
+ (0, typeorm_1.Column)({ default: false }),
83
+ (0, type_graphql_1.Field)({ description: 'Whether this patch was reverted.' }),
84
+ tslib_1.__metadata("design:type", Boolean)
85
+ ], PatchEntry.prototype, "reverted", void 0);
86
+ tslib_1.__decorate([
87
+ (0, typeorm_1.CreateDateColumn)(),
88
+ (0, type_graphql_1.Field)({ nullable: true }),
89
+ tslib_1.__metadata("design:type", Date)
90
+ ], PatchEntry.prototype, "createdAt", void 0);
91
+ exports.PatchEntry = PatchEntry = tslib_1.__decorate([
92
+ (0, typeorm_1.Entity)(),
93
+ (0, typeorm_1.Index)('ix_patch_entry_1', (patch) => [patch.session, patch.createdAt]),
94
+ (0, type_graphql_1.ObjectType)({ description: 'One board edit operation history (cascade-deleted with ChatSession).' })
95
+ ], PatchEntry);
96
+ //# sourceMappingURL=patch-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-entry.js","sourceRoot":"","sources":["../../../server/service/patch-entry/patch-entry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * PatchEntry — 보드의 한 변경 단위 (영구 이력).\n *\n * source 별:\n * - 'ai': AI 가 chat 응답에서 생성\n * - 'user-direct': 사용자가 모델러에서 직접 편집 — 변경 이벤트로 자동 기록\n * - 'import': ImportSession 결과 (도면 → 보드)\n *\n * AI 컨텍스트 유지의 핵심: 사용자 직접 편집도 PatchEntry 로 기록되어\n * 다음 chat 호출 시 system 메시지로 자동 합류 → AI 가 모든 변경을 인지.\n */\nimport {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId\n} from 'typeorm'\nimport { Field, ID, Float, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { config } from '@things-factory/env'\n\nimport { ChatSession } from '../chat-session/chat-session.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n@Index('ix_patch_entry_1', (patch: PatchEntry) => [patch.session, patch.createdAt])\n@ObjectType({ description: 'One board edit operation history (cascade-deleted with ChatSession).' })\nexport class PatchEntry {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => ChatSession, session => session.patches, { onDelete: 'CASCADE' })\n session?: ChatSession\n\n @RelationId((patch: PatchEntry) => patch.session)\n sessionId?: string\n\n /** 'ai' | 'user-direct' | 'import' */\n @Column({ type: 'varchar', length: 32 })\n @Field({ description: \"'ai' | 'user-direct' | 'import'\" })\n source!: string\n\n /** BoardEditOp[] JSON-stringified — column 으로는 long text. */\n @Column({\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n ops!: string\n\n /** GraphQL 노출용 — JSON 객체로 변환. */\n @Field(type => GraphQLJSON, { description: 'BoardEditOp[] (parsed JSON array).' })\n get opsJson(): any {\n try {\n return JSON.parse(this.ops || '[]')\n } catch {\n return []\n }\n }\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Short human summary of this patch.' })\n summary?: string\n\n @Column({ type: 'float', nullable: true })\n @Field(type => Float, { nullable: true, description: 'AI confidence 0..1 (null for user-direct).' })\n confidence?: number\n\n @Column({ default: false })\n @Field({ description: 'Whether this patch was reverted.' })\n reverted!: boolean\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n}\n"]}