@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,705 @@
1
+ /**
2
+ * agentic-loop 단위 테스트 — mock chat + mock dispatch helper 로 isolation.
3
+ *
4
+ * assistant.ts 의 transitive import (board-import → @aws-sdk → @nodable/entities)
5
+ * 를 우회하므로 빠르고 안정적. loop 의 *제어 흐름* 만 검증.
6
+ */
7
+ import {
8
+ runAgenticLoop,
9
+ type ToolDispatchHelpers,
10
+ type AgenticLoopInput
11
+ } from './agentic-loop'
12
+ import type { AIToolChatResult, AIToolCall } from '@things-factory/ai-client-base'
13
+
14
+ // ── helpers ─────────────────────────────────────────────────────
15
+
16
+ const TC = (name: string, args: any = {}, id = 't1'): AIToolCall =>
17
+ ({ id, name, arguments: args, type: 'tool_use' } as any)
18
+
19
+ const okResult = (text?: string, toolCalls: AIToolCall[] = []): AIToolChatResult => ({
20
+ text,
21
+ toolCalls,
22
+ stopReason: toolCalls.length > 0 ? 'tool_use' : 'end_turn',
23
+ usage: undefined as any
24
+ })
25
+
26
+ const baseDispatch = (overrides: Partial<ToolDispatchHelpers> = {}): ToolDispatchHelpers => ({
27
+ isReadTool: () => false,
28
+ isWriteTool: () => false,
29
+ isActionTool: () => false,
30
+ executeReadTool: () => ({ stub: true }),
31
+ validateWriteToolCall: () => ({ valid: true }),
32
+ toolCallToBoardActionOp: () => null,
33
+ summarizeToolResult: v => v,
34
+ ...overrides
35
+ })
36
+
37
+ const mkInput = (
38
+ overrides: Partial<AgenticLoopInput> = {},
39
+ dispatchOverrides: Partial<ToolDispatchHelpers> = {}
40
+ ): AgenticLoopInput => ({
41
+ initialConversation: [{ role: 'user', content: 'hi' }],
42
+ tools: [],
43
+ options: { systemPrompt: 'you are a bot' },
44
+ chat: async () => okResult('default reply'),
45
+ dispatch: baseDispatch(dispatchOverrides),
46
+ currentBoard: undefined,
47
+ selectedRefids: [],
48
+ ...overrides
49
+ })
50
+
51
+ // ── Tests ───────────────────────────────────────────────────────
52
+
53
+ describe('runAgenticLoop — 단일 turn (no tool calls)', () => {
54
+ test('LLM 이 텍스트만 반환 → 즉시 종료', async () => {
55
+ const input = mkInput({ chat: async () => okResult('완료') })
56
+ const r = await runAgenticLoop(input)
57
+ expect(r.lastText).toBe('완료')
58
+ expect(r.accumulatedWriteCalls).toEqual([])
59
+ expect(r.toolUsages).toEqual([])
60
+ expect(r.stopReason).toBe('end_turn')
61
+ })
62
+
63
+ test('빈 reply + 빈 toolCalls — 그래도 정상 종료', async () => {
64
+ const input = mkInput({ chat: async () => okResult() })
65
+ const r = await runAgenticLoop(input)
66
+ expect(r.lastText).toBeUndefined()
67
+ expect(r.accumulatedWriteCalls).toEqual([])
68
+ })
69
+ })
70
+
71
+ describe('runAgenticLoop — read tool 처리', () => {
72
+ test('read tool 호출 → executeReadTool 실행 → 결과 회신 후 LLM 추론 계속', async () => {
73
+ let callCount = 0
74
+ const input = mkInput(
75
+ {
76
+ chat: async () => {
77
+ callCount++
78
+ if (callCount === 1) {
79
+ return okResult(undefined, [TC('getSelection', {})])
80
+ }
81
+ return okResult('finished')
82
+ }
83
+ },
84
+ {
85
+ isReadTool: name => name === 'getSelection',
86
+ executeReadTool: tc => ({ called: tc.name, refids: [1, 2] })
87
+ }
88
+ )
89
+ const r = await runAgenticLoop(input)
90
+ expect(callCount).toBe(2)
91
+ expect(r.lastText).toBe('finished')
92
+ expect(r.toolUsages).toHaveLength(1)
93
+ expect(r.toolUsages[0]).toMatchObject({
94
+ name: 'getSelection',
95
+ kind: 'read',
96
+ result: { called: 'getSelection', refids: [1, 2] }
97
+ })
98
+ })
99
+
100
+ test('read tool 결과가 다음 turn 의 conversation 에 포함', async () => {
101
+ const conversations: any[] = []
102
+ const input = mkInput(
103
+ {
104
+ chat: async msgs => {
105
+ conversations.push(msgs)
106
+ if (conversations.length === 1) return okResult(undefined, [TC('getX')])
107
+ return okResult('done')
108
+ }
109
+ },
110
+ {
111
+ isReadTool: () => true,
112
+ executeReadTool: () => ({ data: 'x' })
113
+ }
114
+ )
115
+ await runAgenticLoop(input)
116
+ // 두 번째 호출 시 conversation 에 assistant tool_use + user tool_result 가 있어야
117
+ expect(conversations).toHaveLength(2)
118
+ const second = conversations[1]
119
+ expect(second.length).toBeGreaterThan(2) // 최소 user / assistant / user
120
+ const lastMsg = second[second.length - 1]
121
+ expect(lastMsg.role).toBe('user')
122
+ expect(lastMsg.content[0]).toMatchObject({
123
+ type: 'tool_result',
124
+ toolUseId: 't1'
125
+ })
126
+ expect(JSON.parse(lastMsg.content[0].content)).toEqual({ data: 'x' })
127
+ })
128
+
129
+ test('summarizeToolResult 가 result 를 변환', async () => {
130
+ let callCount = 0
131
+ const input = mkInput(
132
+ {
133
+ chat: async () => {
134
+ callCount++
135
+ return callCount === 1
136
+ ? okResult(undefined, [TC('listX')])
137
+ : okResult('end')
138
+ }
139
+ },
140
+ {
141
+ isReadTool: () => true,
142
+ executeReadTool: () => ({ items: Array(100).fill('x') }),
143
+ summarizeToolResult: v => ({ summary: 'truncated', count: v.items.length })
144
+ }
145
+ )
146
+ const r = await runAgenticLoop(input)
147
+ expect(r.toolUsages[0].result).toEqual({ summary: 'truncated', count: 100 })
148
+ })
149
+ })
150
+
151
+ describe('runAgenticLoop — write tool 처리', () => {
152
+ test('정상 write — 누적 + queued 결과 회신', async () => {
153
+ let callCount = 0
154
+ const input = mkInput(
155
+ {
156
+ chat: async () => {
157
+ callCount++
158
+ return callCount === 1
159
+ ? okResult(undefined, [TC('setFill', { refids: [1] })])
160
+ : okResult('변경했습니다')
161
+ }
162
+ },
163
+ {
164
+ isWriteTool: name => name === 'setFill',
165
+ validateWriteToolCall: () => ({ valid: true })
166
+ }
167
+ )
168
+ const r = await runAgenticLoop(input)
169
+ expect(r.accumulatedWriteCalls).toHaveLength(1)
170
+ expect(r.accumulatedWriteCalls[0].name).toBe('setFill')
171
+ expect(r.toolUsages[0]).toMatchObject({
172
+ name: 'setFill',
173
+ kind: 'write',
174
+ result: { queued: true }
175
+ })
176
+ })
177
+
178
+ test('validation 실패 — 누적 안 됨, error 회신', async () => {
179
+ let callCount = 0
180
+ const input = mkInput(
181
+ {
182
+ chat: async () => {
183
+ callCount++
184
+ return callCount === 1
185
+ ? okResult(undefined, [TC('setFill', {})])
186
+ : okResult('failed')
187
+ }
188
+ },
189
+ {
190
+ isWriteTool: () => true,
191
+ validateWriteToolCall: () => ({
192
+ valid: false,
193
+ errors: [{ msg: 'refids missing' }],
194
+ suggestion: 'add refids array'
195
+ })
196
+ }
197
+ )
198
+ const r = await runAgenticLoop(input)
199
+ expect(r.accumulatedWriteCalls).toEqual([])
200
+ expect(r.toolUsages[0].result).toMatchObject({
201
+ error: 'Tool args failed validation',
202
+ issues: [{ msg: 'refids missing' }],
203
+ suggestion: 'add refids array'
204
+ })
205
+ })
206
+
207
+ test('일부는 valid, 일부는 invalid — 부분 누적', async () => {
208
+ let callCount = 0
209
+ const input = mkInput(
210
+ {
211
+ chat: async () => {
212
+ callCount++
213
+ return callCount === 1
214
+ ? okResult(undefined, [
215
+ TC('setFill', { refids: [1] }, 't1'),
216
+ TC('setStroke', {}, 't2')
217
+ ])
218
+ : okResult('mixed')
219
+ }
220
+ },
221
+ {
222
+ isWriteTool: () => true,
223
+ validateWriteToolCall: tc =>
224
+ tc.name === 'setFill' ? { valid: true } : { valid: false, errors: ['bad'] }
225
+ }
226
+ )
227
+ const r = await runAgenticLoop(input)
228
+ expect(r.accumulatedWriteCalls).toHaveLength(1)
229
+ expect(r.accumulatedWriteCalls[0].name).toBe('setFill')
230
+ expect(r.toolUsages).toHaveLength(2)
231
+ })
232
+ })
233
+
234
+ describe('runAgenticLoop — action tool 처리', () => {
235
+ test('action tool — accumulatedActions 에 분리 누적', async () => {
236
+ let callCount = 0
237
+ const input = mkInput(
238
+ {
239
+ chat: async () => {
240
+ callCount++
241
+ return callCount === 1
242
+ ? okResult(undefined, [TC('selectComponents', { refids: [7] })])
243
+ : okResult('selected')
244
+ }
245
+ },
246
+ {
247
+ isActionTool: name => name === 'selectComponents',
248
+ toolCallToBoardActionOp: tc => ({
249
+ action: 'selectComponents',
250
+ refids: tc.arguments.refids
251
+ }) as any
252
+ }
253
+ )
254
+ const r = await runAgenticLoop(input)
255
+ expect(r.accumulatedActions).toHaveLength(1)
256
+ expect(r.accumulatedActions[0]).toMatchObject({
257
+ action: 'selectComponents',
258
+ refids: [7]
259
+ })
260
+ expect(r.accumulatedWriteCalls).toEqual([])
261
+ })
262
+
263
+ test('toolCallToBoardActionOp 가 null 반환 — actions 안 누적', async () => {
264
+ let callCount = 0
265
+ const input = mkInput(
266
+ {
267
+ chat: async () => {
268
+ callCount++
269
+ return callCount === 1
270
+ ? okResult(undefined, [TC('badAction', {})])
271
+ : okResult('done')
272
+ }
273
+ },
274
+ {
275
+ isActionTool: () => true,
276
+ toolCallToBoardActionOp: () => null
277
+ }
278
+ )
279
+ const r = await runAgenticLoop(input)
280
+ expect(r.accumulatedActions).toEqual([])
281
+ // 그래도 toolUsages 에는 기록 (queued)
282
+ expect(r.toolUsages).toHaveLength(1)
283
+ })
284
+ })
285
+
286
+ describe('runAgenticLoop — unknown tool 처리', () => {
287
+ test('어떤 분류에도 안 맞음 → error 결과', async () => {
288
+ let callCount = 0
289
+ const input = mkInput({
290
+ chat: async () => {
291
+ callCount++
292
+ return callCount === 1
293
+ ? okResult(undefined, [TC('mysteryTool', {})])
294
+ : okResult('weird')
295
+ }
296
+ })
297
+ const r = await runAgenticLoop(input)
298
+ expect(r.toolUsages[0]).toMatchObject({
299
+ name: 'mysteryTool',
300
+ kind: 'unknown',
301
+ result: { error: 'Unknown tool: mysteryTool' }
302
+ })
303
+ })
304
+ })
305
+
306
+ describe('runAgenticLoop — iteration cap', () => {
307
+ test('LLM 이 계속 tool 호출 → maxIterations 도달 시 종료', async () => {
308
+ let callCount = 0
309
+ const input = mkInput(
310
+ {
311
+ chat: async () => {
312
+ callCount++
313
+ // 매번 read tool 호출 — 무한 루프 가능 시나리오
314
+ return okResult(undefined, [TC('endless')])
315
+ },
316
+ options: { systemPrompt: 'x', maxIterations: 3 }
317
+ },
318
+ {
319
+ isReadTool: () => true,
320
+ executeReadTool: () => ({ ok: true })
321
+ }
322
+ )
323
+ const r = await runAgenticLoop(input)
324
+ expect(callCount).toBe(3)
325
+ expect(r.toolUsages).toHaveLength(3)
326
+ })
327
+
328
+ test('기본 maxIterations 8', async () => {
329
+ let callCount = 0
330
+ const input = mkInput(
331
+ {
332
+ chat: async () => {
333
+ callCount++
334
+ return okResult(undefined, [TC('endless')])
335
+ }
336
+ },
337
+ {
338
+ isReadTool: () => true,
339
+ executeReadTool: () => ({})
340
+ }
341
+ )
342
+ await runAgenticLoop(input)
343
+ expect(callCount).toBe(8)
344
+ })
345
+ })
346
+
347
+ describe('runAgenticLoop — providerMeta round-trip (Gemini thoughtSignature 등)', () => {
348
+ test('tool_use 메시지에 providerMeta 포함', async () => {
349
+ let secondMessages: any[] = []
350
+ let callCount = 0
351
+ const input = mkInput(
352
+ {
353
+ chat: async messages => {
354
+ callCount++
355
+ if (callCount === 1) {
356
+ return okResult(undefined, [
357
+ {
358
+ ...TC('getX'),
359
+ providerMeta: { thoughtSignature: 'gem-12345' }
360
+ } as any
361
+ ])
362
+ }
363
+ secondMessages = messages
364
+ return okResult('end')
365
+ }
366
+ },
367
+ {
368
+ isReadTool: () => true,
369
+ executeReadTool: () => ({})
370
+ }
371
+ )
372
+ await runAgenticLoop(input)
373
+ // assistant turn 의 tool_use content 에 providerMeta 가 살아있어야
374
+ const assistantMsg = secondMessages.find(m => m.role === 'assistant')
375
+ expect(assistantMsg).toBeDefined()
376
+ expect(assistantMsg.content[0]).toMatchObject({
377
+ type: 'tool_use',
378
+ providerMeta: { thoughtSignature: 'gem-12345' }
379
+ })
380
+ })
381
+ })
382
+
383
+ describe('runAgenticLoop — error boundary (provider 예외)', () => {
384
+ test('provider 가 throw → abortReason: provider_error + graceful 종료', async () => {
385
+ const input = mkInput({
386
+ chat: async () => {
387
+ throw new Error('upstream timeout')
388
+ }
389
+ })
390
+ const r = await runAgenticLoop(input)
391
+ expect(r.abortReason).toEqual({
392
+ type: 'provider_error',
393
+ message: 'upstream timeout',
394
+ iter: 0
395
+ })
396
+ expect(r.accumulatedWriteCalls).toEqual([])
397
+ expect(r.toolUsages).toEqual([])
398
+ })
399
+
400
+ test('두 번째 iteration 에서 throw — 첫 iter 결과는 보존', async () => {
401
+ let callCount = 0
402
+ const input = mkInput(
403
+ {
404
+ chat: async () => {
405
+ callCount++
406
+ if (callCount === 1) return okResult(undefined, [TC('getX')])
407
+ throw new Error('500')
408
+ }
409
+ },
410
+ {
411
+ isReadTool: () => true,
412
+ executeReadTool: () => ({ data: 'ok' })
413
+ }
414
+ )
415
+ const r = await runAgenticLoop(input)
416
+ expect(r.abortReason).toMatchObject({ type: 'provider_error', iter: 1 })
417
+ // 첫 iter 의 read tool usage 는 보존
418
+ expect(r.toolUsages).toHaveLength(1)
419
+ expect(r.toolUsages[0].name).toBe('getX')
420
+ })
421
+
422
+ test('AbortError 는 별도 분류 (provider_error 아님)', async () => {
423
+ const err = new Error('aborted')
424
+ ;(err as any).name = 'AbortError'
425
+ const input = mkInput({
426
+ chat: async () => {
427
+ throw err
428
+ }
429
+ })
430
+ const r = await runAgenticLoop(input)
431
+ expect(r.abortReason).toEqual({ type: 'aborted', iter: 0 })
432
+ })
433
+ })
434
+
435
+ describe('runAgenticLoop — AbortSignal', () => {
436
+ test('이미 abort 된 신호 — 첫 iteration 도 안 돔', async () => {
437
+ const ctrl = new AbortController()
438
+ ctrl.abort()
439
+ let chatCalled = false
440
+ const input = mkInput({
441
+ options: { systemPrompt: 'x', signal: ctrl.signal },
442
+ chat: async () => {
443
+ chatCalled = true
444
+ return okResult('should-not-reach')
445
+ }
446
+ })
447
+ const r = await runAgenticLoop(input)
448
+ expect(chatCalled).toBe(false)
449
+ expect(r.abortReason).toEqual({ type: 'aborted', iter: 0 })
450
+ })
451
+
452
+ test('iteration 중간에 abort — 다음 iter 시작 시 종료', async () => {
453
+ const ctrl = new AbortController()
454
+ let callCount = 0
455
+ const input = mkInput(
456
+ {
457
+ options: { systemPrompt: 'x', signal: ctrl.signal },
458
+ chat: async () => {
459
+ callCount++
460
+ if (callCount === 1) {
461
+ ctrl.abort() // 첫 iter 후 abort
462
+ return okResult(undefined, [TC('getX')])
463
+ }
464
+ return okResult('end')
465
+ }
466
+ },
467
+ {
468
+ isReadTool: () => true,
469
+ executeReadTool: () => ({})
470
+ }
471
+ )
472
+ const r = await runAgenticLoop(input)
473
+ // 두 번째 iter 시작 전 abort 감지 → 1번만 호출됨
474
+ expect(callCount).toBe(1)
475
+ expect(r.abortReason).toEqual({ type: 'aborted', iter: 1 })
476
+ // 첫 iter 결과는 보존
477
+ expect(r.toolUsages).toHaveLength(1)
478
+ })
479
+
480
+ test('signal 이 chat 호출에 전달', async () => {
481
+ const ctrl = new AbortController()
482
+ let receivedSignal: any
483
+ const input = mkInput({
484
+ options: { systemPrompt: 'x', signal: ctrl.signal },
485
+ chat: async (_msgs, _tools, opts) => {
486
+ receivedSignal = opts.signal
487
+ return okResult('done')
488
+ }
489
+ })
490
+ await runAgenticLoop(input)
491
+ expect(receivedSignal).toBe(ctrl.signal)
492
+ })
493
+ })
494
+
495
+ describe('runAgenticLoop — repeated validation failure 조기 종료', () => {
496
+ test('같은 tool 3회 연속 fail — 3번째 fail 후 abort', async () => {
497
+ let callCount = 0
498
+ const input = mkInput(
499
+ {
500
+ chat: async () => {
501
+ callCount++
502
+ // 매번 같은 tool 을 같은 invalid args 로 호출
503
+ return okResult(undefined, [TC('setFill', { bad: true })])
504
+ }
505
+ },
506
+ {
507
+ isWriteTool: () => true,
508
+ validateWriteToolCall: () => ({ valid: false, errors: ['always fails'] })
509
+ }
510
+ )
511
+ const r = await runAgenticLoop(input)
512
+ expect(callCount).toBe(3) // 3 회까지 호출 후 abort
513
+ expect(r.abortReason).toEqual({
514
+ type: 'repeated_validation_failure',
515
+ toolName: 'setFill',
516
+ consecutive: 3,
517
+ iter: 2
518
+ })
519
+ })
520
+
521
+ test('limit 설정 (2) — 2회 연속 fail 시 abort', async () => {
522
+ let callCount = 0
523
+ const input = mkInput(
524
+ {
525
+ chat: async () => {
526
+ callCount++
527
+ return okResult(undefined, [TC('setFill', {})])
528
+ },
529
+ options: { systemPrompt: 'x', repeatedFailureLimit: 2 }
530
+ },
531
+ {
532
+ isWriteTool: () => true,
533
+ validateWriteToolCall: () => ({ valid: false, errors: ['x'] })
534
+ }
535
+ )
536
+ const r = await runAgenticLoop(input)
537
+ expect(callCount).toBe(2)
538
+ expect((r.abortReason as any)?.consecutive).toBe(2)
539
+ })
540
+
541
+ test('다른 tool 로 fail 전환 — 카운터 reset', async () => {
542
+ let callCount = 0
543
+ const input = mkInput(
544
+ {
545
+ chat: async () => {
546
+ callCount++
547
+ // 1: setFill fail, 2: setFill fail, 3: setStroke fail (다른 tool)
548
+ // 3 에서 reset 되어 limit 도달 안 함, 4 에서 success 종료
549
+ if (callCount === 1) return okResult(undefined, [TC('setFill', {})])
550
+ if (callCount === 2) return okResult(undefined, [TC('setFill', {})])
551
+ if (callCount === 3) return okResult(undefined, [TC('setStroke', {})])
552
+ return okResult('end')
553
+ }
554
+ },
555
+ {
556
+ isWriteTool: () => true,
557
+ validateWriteToolCall: () => ({ valid: false, errors: ['x'] })
558
+ }
559
+ )
560
+ const r = await runAgenticLoop(input)
561
+ expect(callCount).toBe(4)
562
+ expect(r.abortReason).toBeUndefined()
563
+ })
564
+
565
+ test('성공 사이에 끼어든 fail — 카운터 reset', async () => {
566
+ let callCount = 0
567
+ const input = mkInput(
568
+ {
569
+ chat: async () => {
570
+ callCount++
571
+ // 1: fail, 2: success (read tool), 3: fail, 4: end
572
+ if (callCount === 1) return okResult(undefined, [TC('setFill', {})])
573
+ if (callCount === 2) return okResult(undefined, [TC('getX')])
574
+ if (callCount === 3) return okResult(undefined, [TC('setFill', {})])
575
+ return okResult('end')
576
+ }
577
+ },
578
+ {
579
+ isReadTool: name => name === 'getX',
580
+ isWriteTool: name => name === 'setFill',
581
+ executeReadTool: () => ({}),
582
+ validateWriteToolCall: () => ({ valid: false, errors: ['x'] })
583
+ }
584
+ )
585
+ const r = await runAgenticLoop(input)
586
+ expect(callCount).toBe(4)
587
+ expect(r.abortReason).toBeUndefined()
588
+ })
589
+
590
+ test('valid succeed → 정상 종료, abortReason undefined', async () => {
591
+ let callCount = 0
592
+ const input = mkInput(
593
+ {
594
+ chat: async () => {
595
+ callCount++
596
+ return callCount === 1
597
+ ? okResult(undefined, [TC('setFill', { refids: [1] })])
598
+ : okResult('done')
599
+ }
600
+ },
601
+ {
602
+ isWriteTool: () => true,
603
+ validateWriteToolCall: () => ({ valid: true })
604
+ }
605
+ )
606
+ const r = await runAgenticLoop(input)
607
+ expect(r.abortReason).toBeUndefined()
608
+ expect(r.accumulatedWriteCalls).toHaveLength(1)
609
+ })
610
+ })
611
+
612
+ describe('runAgenticLoop — max iterations 표면화', () => {
613
+ test('max iterations 도달 시 abortReason: max_iterations', async () => {
614
+ const input = mkInput(
615
+ {
616
+ chat: async () => okResult(undefined, [TC('endless')]),
617
+ options: { systemPrompt: 'x', maxIterations: 2 }
618
+ },
619
+ {
620
+ isReadTool: () => true,
621
+ executeReadTool: () => ({})
622
+ }
623
+ )
624
+ const r = await runAgenticLoop(input)
625
+ expect(r.abortReason).toEqual({ type: 'max_iterations', iter: 2 })
626
+ })
627
+
628
+ test('정상 종료 (toolCalls 비어 break) 시 abortReason undefined', async () => {
629
+ const input = mkInput({ chat: async () => okResult('end') })
630
+ const r = await runAgenticLoop(input)
631
+ expect(r.abortReason).toBeUndefined()
632
+ })
633
+ })
634
+
635
+ describe('runAgenticLoop — signal 이 chat 호출에 전달', () => {
636
+ test('signal undefined 시 chat opts.signal 도 undefined', async () => {
637
+ let receivedSignal: any = 'unset'
638
+ const input = mkInput({
639
+ chat: async (_msgs, _tools, opts) => {
640
+ receivedSignal = opts.signal
641
+ return okResult('done')
642
+ }
643
+ })
644
+ await runAgenticLoop(input)
645
+ expect(receivedSignal).toBeUndefined()
646
+ })
647
+ })
648
+
649
+ describe('runAgenticLoop — currentBoard / selectedRefids 전달', () => {
650
+ test('executeReadTool 에 board / selection 그대로 전달', async () => {
651
+ const board = { width: 100, height: 100, components: [{ refid: 1 }] } as any
652
+ let received: any
653
+ let callCount = 0
654
+ const input = mkInput(
655
+ {
656
+ chat: async () => {
657
+ callCount++
658
+ return callCount === 1
659
+ ? okResult(undefined, [TC('peek')])
660
+ : okResult('done')
661
+ },
662
+ currentBoard: board,
663
+ selectedRefids: [1]
664
+ },
665
+ {
666
+ isReadTool: () => true,
667
+ executeReadTool: (tc, b, refids) => {
668
+ received = { tcName: tc.name, board: b, refids }
669
+ return {}
670
+ }
671
+ }
672
+ )
673
+ await runAgenticLoop(input)
674
+ expect(received).toEqual({
675
+ tcName: 'peek',
676
+ board,
677
+ refids: [1]
678
+ })
679
+ })
680
+
681
+ test('write tool 의 validation 에 components 전달', async () => {
682
+ let callCount = 0
683
+ let receivedComponents: any
684
+ const input = mkInput(
685
+ {
686
+ chat: async () => {
687
+ callCount++
688
+ return callCount === 1
689
+ ? okResult(undefined, [TC('setFill', {})])
690
+ : okResult('done')
691
+ },
692
+ currentBoard: { width: 10, height: 10, components: [{ refid: 7 }] } as any
693
+ },
694
+ {
695
+ isWriteTool: () => true,
696
+ validateWriteToolCall: (_tc, components) => {
697
+ receivedComponents = components
698
+ return { valid: true }
699
+ }
700
+ }
701
+ )
702
+ await runAgenticLoop(input)
703
+ expect(receivedComponents).toEqual([{ refid: 7 }])
704
+ })
705
+ })