@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,431 @@
1
+ /**
2
+ * agentic-loop — multi-turn LLM ↔ tool 루프.
3
+ *
4
+ * assistant.ts 에서 추출 — loop 자체를 *순수 함수* 로 만들어:
5
+ * 1. 단위 테스트 가능 (assistant.ts 의 board-import → @aws-sdk transitive 경로 회피)
6
+ * 2. 관측성 (error boundary / iter log / AbortSignal / repeated-failure 조기 종료)
7
+ * 을 한 곳에 응집
8
+ * 3. 향후 split (Phase 3) 의 자연 경계
9
+ *
10
+ * 입력 — `chat` 함수와 dispatch helper 들을 의존성 주입. board-ai 본체는 본 모듈에
11
+ * 의존하지만 본 모듈은 board-ai 의 어떤 무거운 import 도 모름.
12
+ */
13
+ import debug from 'debug'
14
+ import type {
15
+ AIMessage,
16
+ AIToolCall,
17
+ AIToolChatResult
18
+ } from '@things-factory/ai-client-base'
19
+ import type { BoardModel } from '@things-factory/board-import'
20
+ import type { BoardActionOp, ToolUsage } from './types'
21
+
22
+ const log = debug('things-factory:board-ai:loop')
23
+
24
+ // ── Public types ─────────────────────────────────────────────────
25
+
26
+ /**
27
+ * Loop 가 호출하는 dispatch 함수들. 모두 의존성 주입.
28
+ *
29
+ * assistant.ts 가 자기 자신의 함수 (isReadTool, executeReadTool 등) 를 그대로
30
+ * 넘겨주는 구조. 본 모듈은 이 함수들의 *시그니처* 만 알면 됨.
31
+ */
32
+ export interface ToolDispatchHelpers {
33
+ isReadTool: (name: string) => boolean
34
+ isWriteTool: (name: string) => boolean
35
+ isActionTool: (name: string) => boolean
36
+ executeReadTool: (
37
+ tc: AIToolCall,
38
+ board: BoardModel | undefined,
39
+ selectedRefids: number[]
40
+ ) => any
41
+ validateWriteToolCall: (
42
+ tc: AIToolCall,
43
+ components: any[]
44
+ ) => { valid: boolean; errors?: any[]; suggestion?: string }
45
+ toolCallToBoardActionOp: (tc: AIToolCall) => BoardActionOp | null
46
+ summarizeToolResult: (value: any) => any
47
+ }
48
+
49
+ export interface AgenticLoopOptions {
50
+ systemPrompt: string
51
+ model?: string
52
+ temperature?: number
53
+ maxTokens?: number
54
+ /** 최대 iteration. 기본 8. */
55
+ maxIterations?: number
56
+ /**
57
+ * 사용자 abort 신호. iteration 시작 직전 / chat() 호출 후 체크.
58
+ * 이미 abort 된 신호로 들어오면 첫 iteration 도 안 돔.
59
+ */
60
+ signal?: AbortSignal
61
+ /**
62
+ * 같은 tool 의 validation 실패가 N회 연속이면 조기 종료. 기본 3.
63
+ * LLM 이 같은 잘못을 무한 반복하는 경우 방어.
64
+ */
65
+ repeatedFailureLimit?: number
66
+ }
67
+
68
+ export interface AgenticLoopInput {
69
+ initialConversation: AIMessage[]
70
+ tools: any[]
71
+ options: AgenticLoopOptions
72
+ /** LLM provider 의 chatWithTools 위임. provider-specific 구현. */
73
+ chat: (
74
+ messages: AIMessage[],
75
+ tools: any[],
76
+ options: any
77
+ ) => Promise<AIToolChatResult>
78
+ dispatch: ToolDispatchHelpers
79
+ currentBoard: BoardModel | undefined
80
+ selectedRefids: number[]
81
+ }
82
+
83
+ export interface AgenticLoopResult {
84
+ lastText: string | undefined
85
+ accumulatedWriteCalls: AIToolCall[]
86
+ accumulatedActions: BoardActionOp[]
87
+ toolUsages: ToolUsage[]
88
+ /** LLM 마지막 응답의 stopReason. provider-specific 문자열. */
89
+ stopReason: string
90
+ /**
91
+ * 비정상 종료 사유 — 정상 종료 시 undefined.
92
+ * 호출자 (assistant.ts) 가 사용자 메시지에 반영 (예: '시도 중 오류가 발생했어요').
93
+ */
94
+ abortReason?:
95
+ | { type: 'provider_error'; message: string; iter: number }
96
+ | { type: 'aborted'; iter: number }
97
+ | { type: 'max_iterations'; iter: number }
98
+ | { type: 'repeated_validation_failure'; toolName: string; consecutive: number; iter: number }
99
+ }
100
+
101
+ // ── Loop ─────────────────────────────────────────────────────────
102
+
103
+ const DEFAULT_MAX_ITERATIONS = 8
104
+
105
+ /**
106
+ * multi-turn agentic loop.
107
+ *
108
+ * LLM 이 read tool 을 호출 → 서버에서 즉시 실행 → 결과 회신 → LLM 추론 반복.
109
+ * write tool 은 누적해 client 에 patch 로 전달. 무한 루프 방지를 위해 iteration cap.
110
+ *
111
+ * 본 함수는 *순수* — input 만 보고 결정. 외부 mutable state 없음.
112
+ */
113
+ export async function runAgenticLoop(
114
+ input: AgenticLoopInput
115
+ ): Promise<AgenticLoopResult> {
116
+ const {
117
+ initialConversation,
118
+ tools,
119
+ options,
120
+ chat,
121
+ dispatch,
122
+ currentBoard,
123
+ selectedRefids
124
+ } = input
125
+
126
+ const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS
127
+ const failureLimit = options.repeatedFailureLimit ?? 3
128
+ const signal = options.signal
129
+
130
+ let conversation = initialConversation
131
+ const accumulatedWriteCalls: AIToolCall[] = []
132
+ const accumulatedActions: BoardActionOp[] = []
133
+ const toolUsages: ToolUsage[] = []
134
+ let lastText: string | undefined
135
+ let stopReason: string = 'end_turn'
136
+ let abortReason: AgenticLoopResult['abortReason']
137
+
138
+ // 같은 tool 의 validation 실패 연속 카운터 — 다른 tool 호출 시 reset
139
+ let lastFailedToolName: string | undefined
140
+ let consecutiveFailures = 0
141
+
142
+ let iter = 0
143
+ for (; iter < maxIterations; iter++) {
144
+ // (1) AbortSignal — iteration 시작 시 체크
145
+ if (signal?.aborted) {
146
+ log('iter %d: aborted by signal', iter)
147
+ abortReason = { type: 'aborted', iter }
148
+ break
149
+ }
150
+
151
+ log('iter %d: chat() 호출', iter)
152
+ let result: AIToolChatResult
153
+ try {
154
+ // (2) Error boundary — provider 예외 시 graceful 종료
155
+ result = await chat(conversation, tools, {
156
+ systemPrompt: options.systemPrompt,
157
+ model: options.model,
158
+ temperature: options.temperature,
159
+ maxTokens: options.maxTokens,
160
+ toolChoice: 'auto',
161
+ allowParallelToolCalls: true,
162
+ signal
163
+ })
164
+ } catch (e: any) {
165
+ // AbortError — 별도 분류
166
+ if (e?.name === 'AbortError' || signal?.aborted) {
167
+ log('iter %d: provider AbortError', iter)
168
+ abortReason = { type: 'aborted', iter }
169
+ } else {
170
+ log('iter %d: provider error: %s', iter, e?.message)
171
+ abortReason = {
172
+ type: 'provider_error',
173
+ message: e?.message ?? String(e),
174
+ iter
175
+ }
176
+ }
177
+ break
178
+ }
179
+
180
+ lastText = result.text || lastText
181
+ stopReason = result.stopReason
182
+
183
+ log(
184
+ 'iter %d: text=%s, toolCalls=%d, stopReason=%s',
185
+ iter,
186
+ result.text ? `"${result.text.slice(0, 60)}..."` : '(none)',
187
+ result.toolCalls.length,
188
+ result.stopReason
189
+ )
190
+
191
+ if (result.toolCalls.length === 0) break
192
+
193
+ // (1b) 삭제: chat() 직후 abort 는 다음 iter 시작 시 (1) 에서 감지.
194
+ // 현재 iter tool 실행을 중단하면 read tool 결과도 버려져 테스트/동작 불일치.
195
+
196
+ // tool 결과 빌드 — read 는 즉시 실행, write 는 누적, action 은 별도 채널
197
+ const beforeUsages = toolUsages.length
198
+ const toolResultParts = await processToolCalls(
199
+ result.toolCalls,
200
+ currentBoard,
201
+ selectedRefids,
202
+ dispatch,
203
+ accumulatedWriteCalls,
204
+ accumulatedActions,
205
+ toolUsages
206
+ )
207
+
208
+ // (4) 같은 tool 의 validation 실패 연속 감지 — LLM 이 같은 잘못 반복 시 abort
209
+ const newUsages = toolUsages.slice(beforeUsages)
210
+ const repeatedFailureAbort = detectRepeatedValidationFailure(
211
+ newUsages,
212
+ lastFailedToolName,
213
+ consecutiveFailures,
214
+ failureLimit
215
+ )
216
+ if (repeatedFailureAbort.shouldAbort) {
217
+ log(
218
+ 'iter %d: repeated validation failure on %s (%d consecutive) — abort',
219
+ iter,
220
+ repeatedFailureAbort.toolName,
221
+ repeatedFailureAbort.consecutive
222
+ )
223
+ abortReason = {
224
+ type: 'repeated_validation_failure',
225
+ toolName: repeatedFailureAbort.toolName,
226
+ consecutive: repeatedFailureAbort.consecutive,
227
+ iter
228
+ }
229
+ break
230
+ }
231
+ lastFailedToolName = repeatedFailureAbort.lastFailedToolName
232
+ consecutiveFailures = repeatedFailureAbort.consecutive
233
+
234
+ // assistant 의 tool_use turn + user 의 tool_result turn 을 conversation 에 누적
235
+ const assistantContent: any[] = []
236
+ if (result.text) assistantContent.push({ type: 'text', text: result.text })
237
+ for (const tc of result.toolCalls) {
238
+ // providerMeta (Gemini thoughtSignature 등) 를 그대로 round-trip — 안 보내면 400.
239
+ assistantContent.push({
240
+ type: 'tool_use',
241
+ id: tc.id,
242
+ name: tc.name,
243
+ arguments: tc.arguments,
244
+ ...(tc.providerMeta && { providerMeta: tc.providerMeta })
245
+ })
246
+ }
247
+
248
+ conversation = [
249
+ ...conversation,
250
+ { role: 'assistant', content: assistantContent },
251
+ { role: 'user', content: toolResultParts }
252
+ ]
253
+ }
254
+
255
+ // (3) max iterations 도달 시 사용자에게 표면화
256
+ if (iter >= maxIterations && !abortReason) {
257
+ log('iter %d: max iterations reached', iter)
258
+ abortReason = { type: 'max_iterations', iter }
259
+ }
260
+
261
+ return {
262
+ lastText,
263
+ accumulatedWriteCalls,
264
+ accumulatedActions,
265
+ toolUsages,
266
+ stopReason,
267
+ abortReason
268
+ }
269
+ }
270
+
271
+ /**
272
+ * 같은 tool 의 validation 실패 연속 감지 — pure helper.
273
+ *
274
+ * 같은 tool 이 반복 fail 하면 LLM 이 self-correct 못 하는 상태.
275
+ * 다른 tool 이 fail 하면 시퀀스 새로 시작 (전혀 다른 패턴이라).
276
+ * tool 이 succeed 하면 카운터 reset.
277
+ */
278
+ function detectRepeatedValidationFailure(
279
+ newUsages: ToolUsage[],
280
+ lastFailedToolName: string | undefined,
281
+ consecutiveFailures: number,
282
+ limit: number
283
+ ): {
284
+ shouldAbort: boolean
285
+ toolName: string
286
+ consecutive: number
287
+ lastFailedToolName: string | undefined
288
+ } {
289
+ // 이번 iteration 에서 실패한 write tool 들 (validation 거절)
290
+ const failed = newUsages.filter(
291
+ u => u.kind === 'write' && (u.result as any)?.error === 'Tool args failed validation'
292
+ )
293
+
294
+ if (failed.length === 0) {
295
+ // 실패 없음 — reset
296
+ return {
297
+ shouldAbort: false,
298
+ toolName: '',
299
+ consecutive: 0,
300
+ lastFailedToolName: undefined
301
+ }
302
+ }
303
+
304
+ // 모든 실패가 같은 tool 인지 확인
305
+ const firstName = failed[0].name
306
+ const allSame = failed.every(u => u.name === firstName)
307
+
308
+ if (allSame && firstName === lastFailedToolName) {
309
+ const consecutive = consecutiveFailures + 1
310
+ return {
311
+ shouldAbort: consecutive >= limit,
312
+ toolName: firstName,
313
+ consecutive,
314
+ lastFailedToolName: firstName
315
+ }
316
+ }
317
+
318
+ // 다른 tool 또는 첫 실패
319
+ return {
320
+ shouldAbort: false,
321
+ toolName: firstName,
322
+ consecutive: 1,
323
+ lastFailedToolName: firstName
324
+ }
325
+ }
326
+
327
+ /**
328
+ * 한 iteration 의 tool 결과를 분류·실행·누적. (loop 에서 분리해 읽기 쉽게.)
329
+ */
330
+ async function processToolCalls(
331
+ toolCalls: AIToolCall[],
332
+ currentBoard: BoardModel | undefined,
333
+ selectedRefids: number[],
334
+ dispatch: ToolDispatchHelpers,
335
+ accumulatedWriteCalls: AIToolCall[],
336
+ accumulatedActions: BoardActionOp[],
337
+ toolUsages: ToolUsage[]
338
+ ): Promise<any[]> {
339
+ const toolResultParts: any[] = []
340
+
341
+ for (const tc of toolCalls) {
342
+ if (dispatch.isReadTool(tc.name)) {
343
+ const value = dispatch.executeReadTool(tc, currentBoard, selectedRefids)
344
+ toolResultParts.push({
345
+ type: 'tool_result',
346
+ toolUseId: tc.id,
347
+ content: JSON.stringify(value)
348
+ })
349
+ toolUsages.push({
350
+ name: tc.name,
351
+ arguments: tc.arguments ?? {},
352
+ result: dispatch.summarizeToolResult(value),
353
+ kind: 'read'
354
+ })
355
+ } else if (dispatch.isWriteTool(tc.name)) {
356
+ // 사전 검증 — args 가 schema / build 함수 검증 통과해야 누적.
357
+ // 실패 시 LLM 한테 detailed error 반환 → 다음 turn 에서 자기 회복 (retry).
358
+ const components = (currentBoard?.components ?? []) as any[]
359
+ const validation = dispatch.validateWriteToolCall(tc, components)
360
+ if (!validation.valid) {
361
+ const errorResult = {
362
+ error: 'Tool args failed validation',
363
+ issues: validation.errors,
364
+ suggestion: validation.suggestion,
365
+ note: 'Fix the args and call the tool again.'
366
+ }
367
+ toolResultParts.push({
368
+ type: 'tool_result',
369
+ toolUseId: tc.id,
370
+ content: JSON.stringify(errorResult)
371
+ })
372
+ toolUsages.push({
373
+ name: tc.name,
374
+ arguments: tc.arguments ?? {},
375
+ result: errorResult,
376
+ kind: 'write'
377
+ })
378
+ // continue — 누적 안 함. LLM 이 다음 turn 에서 정정 호출.
379
+ } else {
380
+ accumulatedWriteCalls.push(tc)
381
+ const queuedResult = { queued: true, note: 'Will be applied on the client.' }
382
+ toolResultParts.push({
383
+ type: 'tool_result',
384
+ toolUseId: tc.id,
385
+ content: JSON.stringify(queuedResult)
386
+ })
387
+ toolUsages.push({
388
+ name: tc.name,
389
+ arguments: tc.arguments ?? {},
390
+ result: queuedResult,
391
+ kind: 'write'
392
+ })
393
+ }
394
+ } else if (dispatch.isActionTool(tc.name)) {
395
+ // ephemeral scene 조작 — accumulatedActions 로 누적, ChatResponse.actions 로 client 전달
396
+ const action = dispatch.toolCallToBoardActionOp(tc)
397
+ if (action) accumulatedActions.push(action)
398
+ const queuedResult = {
399
+ queued: true,
400
+ note: 'Will be executed on the modeller (ephemeral).'
401
+ }
402
+ toolResultParts.push({
403
+ type: 'tool_result',
404
+ toolUseId: tc.id,
405
+ content: JSON.stringify(queuedResult)
406
+ })
407
+ toolUsages.push({
408
+ name: tc.name,
409
+ arguments: tc.arguments ?? {},
410
+ result: queuedResult,
411
+ kind: 'write'
412
+ })
413
+ } else {
414
+ const errResult = { error: `Unknown tool: ${tc.name}` }
415
+ toolResultParts.push({
416
+ type: 'tool_result',
417
+ toolUseId: tc.id,
418
+ content: JSON.stringify(errResult),
419
+ isError: true
420
+ })
421
+ toolUsages.push({
422
+ name: tc.name,
423
+ arguments: tc.arguments ?? {},
424
+ result: errResult,
425
+ kind: 'unknown'
426
+ })
427
+ }
428
+ }
429
+
430
+ return toolResultParts
431
+ }