akanjs 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (388) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -1
  3. package/base/symbols.ts +4 -0
  4. package/client/clientRuntime.ts +8 -0
  5. package/client/cn.ts +7 -0
  6. package/client/csrTypes.ts +2 -0
  7. package/client/frameDebug.ts +3 -2
  8. package/common/Logger.ts +4 -0
  9. package/common/deepObjectify.ts +21 -7
  10. package/common/index.ts +14 -0
  11. package/common/mcpExposure.ts +99 -0
  12. package/common/pathSet.ts +17 -5
  13. package/common/routeConvention.ts +30 -0
  14. package/constant/crystalize.ts +3 -1
  15. package/constant/fieldInfo.ts +6 -0
  16. package/constant/getDefault.ts +37 -9
  17. package/constant/immerify.ts +1 -1
  18. package/constant/index.ts +2 -0
  19. package/constant/labelOf.ts +17 -0
  20. package/constant/mask.ts +60 -0
  21. package/constant/purify.ts +17 -11
  22. package/constant/types.ts +14 -12
  23. package/dictionary/DictionaryLookup.ts +30 -0
  24. package/dictionary/agent.dictionary.ts +31 -0
  25. package/dictionary/agentTurn.dictionary.ts +29 -0
  26. package/dictionary/base.dictionary.ts +8 -0
  27. package/dictionary/dictInfo.ts +8 -0
  28. package/dictionary/dictionary.ts +9 -3
  29. package/dictionary/index.ts +1 -0
  30. package/document/database.ts +21 -0
  31. package/document/filterMeta.ts +7 -0
  32. package/document/index.ts +1 -0
  33. package/document/into.ts +7 -1
  34. package/document/noDocumentError.ts +12 -0
  35. package/fetch/agentTurn.ts +19 -0
  36. package/fetch/client/fetchClient.ts +71 -13
  37. package/fetch/client/wsClient.ts +17 -2
  38. package/fetch/fetchType/endpointFetch.type.ts +10 -4
  39. package/fetch/index.ts +1 -0
  40. package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
  41. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  42. package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
  43. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
  44. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  45. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
  46. package/package.json +11 -9
  47. package/server/akanApp.ts +30 -34
  48. package/server/akanOption.ts +65 -7
  49. package/server/akanServer.ts +172 -7
  50. package/server/animatedImage.ts +83 -0
  51. package/server/artifact/routeClientCache.ts +14 -0
  52. package/server/assetEncoding.ts +47 -0
  53. package/server/devtools/signalSerializer.ts +2 -2
  54. package/server/devtools/types.ts +2 -2
  55. package/server/di/diLifecycle.ts +31 -3
  56. package/server/di/predefinedAdaptor.ts +6 -0
  57. package/server/imageOptimizer.ts +33 -46
  58. package/server/index.ts +1 -2
  59. package/server/mcp/McpAuth.ts +177 -0
  60. package/server/mcp/McpDispatcher.ts +245 -0
  61. package/server/mcp/McpEventStream.ts +76 -0
  62. package/server/mcp/McpExecutionContext.ts +105 -0
  63. package/server/mcp/McpRouter.ts +591 -0
  64. package/server/mcp/index.ts +5 -0
  65. package/server/resolver/CascadeRunner.ts +1 -1
  66. package/server/resolver/database.resolver.ts +42 -5
  67. package/server/resolver/service.resolver.ts +29 -0
  68. package/server/resolver/signal.resolver.ts +16 -1
  69. package/server/routeTreeBuilder.ts +2 -44
  70. package/server/rscWorker.tsx +6 -2
  71. package/server/subRouteIndexDocument.tsx +61 -0
  72. package/server/systemPageDocument.tsx +2 -2
  73. package/server/systemPages.tsx +49 -16
  74. package/server/vendor/akanjs-fetch.ts +1 -0
  75. package/server/webRouter.ts +159 -35
  76. package/service/agent.service.ts +14 -0
  77. package/service/base.service.ts +2 -1
  78. package/service/index.ts +1 -0
  79. package/service/predefinedAdaptor/database.adaptor.ts +78 -24
  80. package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
  81. package/service/predefinedAdaptor/index.ts +3 -0
  82. package/service/predefinedAdaptor/insightQuery.ts +183 -0
  83. package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
  84. package/service/predefinedAdaptor/role.adaptor.ts +2 -0
  85. package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
  86. package/service/serviceModule.ts +17 -0
  87. package/service/types.ts +4 -0
  88. package/signal/agent/AgentCatalogue.ts +165 -0
  89. package/signal/agent/index.ts +1 -0
  90. package/signal/agent.signal.ts +42 -0
  91. package/signal/agentTurn.ts +8 -0
  92. package/signal/agentTurnStream.ts +48 -0
  93. package/signal/base.signal.ts +2 -1
  94. package/signal/endpointInfo.ts +38 -2
  95. package/signal/guard.ts +13 -1
  96. package/signal/guards.ts +52 -1
  97. package/signal/index.ts +6 -0
  98. package/signal/mcp/McpDocument.ts +286 -0
  99. package/signal/mcp/McpProgress.ts +106 -0
  100. package/signal/mcp/McpUriTemplate.ts +85 -0
  101. package/signal/mcp/Msg.ts +368 -0
  102. package/signal/mcp/index.ts +5 -0
  103. package/signal/mcp/mcpProtocol.ts +120 -0
  104. package/signal/middleware.ts +7 -3
  105. package/signal/openapi/openapi.ts +32 -171
  106. package/signal/schema/JsonSchemaBuilder.ts +211 -0
  107. package/signal/schema/index.ts +1 -0
  108. package/signal/signalContext.ts +85 -9
  109. package/signal/slice.ts +9 -9
  110. package/signal/types.ts +1 -1
  111. package/store/action.ts +11 -22
  112. package/store/actionTag.ts +33 -0
  113. package/store/agent/AgentBridge.ts +323 -0
  114. package/store/agent/AgentContext.ts +74 -0
  115. package/store/agent/AgentPrompts.ts +82 -0
  116. package/store/agent/AgentVisibility.ts +68 -0
  117. package/store/agent/ScreenReader.ts +208 -0
  118. package/store/agent/StoreCatalogue.ts +351 -0
  119. package/store/agent/StoreSurfaceSource.ts +122 -0
  120. package/store/agent/index.ts +9 -0
  121. package/store/agent/storeSurface.ts +28 -0
  122. package/store/agent/types.ts +50 -0
  123. package/store/agentic/StToolBuilder.ts +199 -0
  124. package/store/agentic/attachAgentic.ts +22 -0
  125. package/store/agentic/index.ts +5 -0
  126. package/store/agentic/readableValue.ts +17 -0
  127. package/store/agentic/useStExpose.ts +18 -0
  128. package/store/agentic/useStState.ts +35 -0
  129. package/store/baseSt.ts +7 -0
  130. package/store/databaseStateNames.ts +31 -0
  131. package/store/formSetterNames.ts +21 -0
  132. package/store/index.ts +8 -0
  133. package/store/rootStore.ts +3 -1
  134. package/store/sliceRole.ts +36 -0
  135. package/store/state.ts +2 -12
  136. package/store/store.ts +19 -1
  137. package/store/storeInstance.ts +165 -20
  138. package/store/storeRegistry.ts +16 -1
  139. package/store/types.ts +18 -0
  140. package/store/withSelector.ts +7 -8
  141. package/test/registerDom.ts +4 -0
  142. package/types/base/symbols.d.ts +4 -0
  143. package/types/client/cn.d.ts +5 -0
  144. package/types/client/csrTypes.d.ts +2 -0
  145. package/types/common/Logger.d.ts +2 -0
  146. package/types/common/deepObjectify.d.ts +4 -2
  147. package/types/common/index.d.ts +2 -1
  148. package/types/common/mcpExposure.d.ts +60 -0
  149. package/types/common/routeConvention.d.ts +8 -0
  150. package/types/constant/fieldInfo.d.ts +1 -0
  151. package/types/constant/getDefault.d.ts +5 -0
  152. package/types/constant/index.d.ts +2 -0
  153. package/types/constant/labelOf.d.ts +6 -0
  154. package/types/constant/mask.d.ts +34 -0
  155. package/types/constant/purify.d.ts +2 -2
  156. package/types/constant/types.d.ts +2 -2
  157. package/types/dictionary/DictionaryLookup.d.ts +14 -0
  158. package/types/dictionary/agent.dictionary.d.ts +1 -0
  159. package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
  160. package/types/dictionary/base.dictionary.d.ts +1 -1
  161. package/types/dictionary/dictInfo.d.ts +6 -0
  162. package/types/dictionary/dictionary.d.ts +11 -9
  163. package/types/dictionary/index.d.ts +1 -0
  164. package/types/document/database.d.ts +20 -0
  165. package/types/document/filterMeta.d.ts +1 -0
  166. package/types/document/index.d.ts +1 -0
  167. package/types/document/into.d.ts +6 -1
  168. package/types/document/noDocumentError.d.ts +12 -0
  169. package/types/fetch/agentTurn.d.ts +593 -0
  170. package/types/fetch/client/fetchClient.d.ts +10 -0
  171. package/types/fetch/fetchType/endpointFetch.type.d.ts +5 -3
  172. package/types/fetch/index.d.ts +1 -0
  173. package/types/server/akanOption.d.ts +35 -7
  174. package/types/server/akanServer.d.ts +44 -1
  175. package/types/server/animatedImage.d.ts +7 -0
  176. package/types/server/artifact/routeClientCache.d.ts +6 -0
  177. package/types/server/assetEncoding.d.ts +7 -0
  178. package/types/server/devtools/types.d.ts +2 -2
  179. package/types/server/di/predefinedAdaptor.d.ts +4 -1
  180. package/types/server/index.d.ts +0 -2
  181. package/types/server/mcp/McpAuth.d.ts +62 -0
  182. package/types/server/mcp/McpDispatcher.d.ts +54 -0
  183. package/types/server/mcp/McpEventStream.d.ts +18 -0
  184. package/types/server/mcp/McpExecutionContext.d.ts +44 -0
  185. package/types/server/mcp/McpRouter.d.ts +60 -0
  186. package/types/server/mcp/index.d.ts +5 -0
  187. package/types/server/mcp.d.ts +1 -0
  188. package/types/server/subRouteIndexDocument.d.ts +8 -0
  189. package/types/server/systemPageDocument.d.ts +1 -0
  190. package/types/server/systemPages.d.ts +5 -0
  191. package/types/server/vendor/akanjs-fetch.d.ts +1 -0
  192. package/types/service/agent.service.d.ts +12 -0
  193. package/types/service/base.service.d.ts +4 -0
  194. package/types/service/index.d.ts +1 -0
  195. package/types/service/predefinedAdaptor/database.adaptor.d.ts +24 -2
  196. package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
  197. package/types/service/predefinedAdaptor/index.d.ts +3 -0
  198. package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
  199. package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
  200. package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
  201. package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
  202. package/types/service/types.d.ts +4 -1
  203. package/types/signal/agent/AgentCatalogue.d.ts +100 -0
  204. package/types/signal/agent/index.d.ts +1 -0
  205. package/types/signal/agent.d.ts +1 -0
  206. package/types/signal/agent.signal.d.ts +29 -0
  207. package/types/signal/agentTurn.d.ts +9 -0
  208. package/types/signal/agentTurnStream.d.ts +16 -0
  209. package/types/signal/base.signal.d.ts +1 -1
  210. package/types/signal/endpointInfo.d.ts +21 -3
  211. package/types/signal/guard.d.ts +11 -0
  212. package/types/signal/guards.d.ts +22 -1
  213. package/types/signal/index.d.ts +6 -0
  214. package/types/signal/mcp/McpDocument.d.ts +65 -0
  215. package/types/signal/mcp/McpProgress.d.ts +40 -0
  216. package/types/signal/mcp/McpUriTemplate.d.ts +26 -0
  217. package/types/signal/mcp/Msg.d.ts +143 -0
  218. package/types/signal/mcp/index.d.ts +5 -0
  219. package/types/signal/mcp/mcpProtocol.d.ts +107 -0
  220. package/types/signal/mcp.d.ts +1 -0
  221. package/types/signal/openapi/openapi.d.ts +3 -3
  222. package/types/signal/schema/JsonSchemaBuilder.d.ts +47 -0
  223. package/types/signal/schema/index.d.ts +1 -0
  224. package/types/signal/schema.d.ts +1 -0
  225. package/types/signal/signalContext.d.ts +16 -1
  226. package/types/signal/slice.d.ts +8 -8
  227. package/types/signal/types.d.ts +1 -1
  228. package/types/store/actionTag.d.ts +21 -0
  229. package/types/store/agent/AgentBridge.d.ts +77 -0
  230. package/types/store/agent/AgentContext.d.ts +16 -0
  231. package/types/store/agent/AgentPrompts.d.ts +31 -0
  232. package/types/store/agent/AgentVisibility.d.ts +21 -0
  233. package/types/store/agent/ScreenReader.d.ts +11 -0
  234. package/types/store/agent/StoreCatalogue.d.ts +26 -0
  235. package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
  236. package/types/store/agent/index.d.ts +9 -0
  237. package/types/store/agent/storeSurface.d.ts +14 -0
  238. package/types/store/agent/types.d.ts +49 -0
  239. package/types/store/agent.d.ts +1 -0
  240. package/types/store/agentic/StToolBuilder.d.ts +41 -0
  241. package/types/store/agentic/attachAgentic.d.ts +14 -0
  242. package/types/store/agentic/index.d.ts +5 -0
  243. package/types/store/agentic/readableValue.d.ts +3 -0
  244. package/types/store/agentic/useStExpose.d.ts +8 -0
  245. package/types/store/agentic/useStState.d.ts +12 -0
  246. package/types/store/agentic.d.ts +1 -0
  247. package/types/store/baseSt.d.ts +29 -22
  248. package/types/store/databaseStateNames.d.ts +25 -0
  249. package/types/store/formSetterNames.d.ts +16 -0
  250. package/types/store/index.d.ts +7 -0
  251. package/types/store/rootStore.d.ts +5 -1
  252. package/types/store/sliceRole.d.ts +25 -0
  253. package/types/store/store.d.ts +7 -2
  254. package/types/store/storeInstance.d.ts +33 -1
  255. package/types/store/storeRegistry.d.ts +5 -0
  256. package/types/store/types.d.ts +15 -0
  257. package/types/store/withSelector.d.ts +7 -8
  258. package/types/test/registerDom.d.ts +1 -0
  259. package/types/ui/Agent/Approval.d.ts +7 -0
  260. package/types/ui/Agent/Bubble.d.ts +13 -0
  261. package/types/ui/Agent/Chat.d.ts +24 -0
  262. package/types/ui/Agent/Context.d.ts +9 -0
  263. package/types/ui/Agent/Dock.d.ts +16 -0
  264. package/types/ui/Agent/Guide.d.ts +9 -0
  265. package/types/ui/Agent/Section.d.ts +10 -0
  266. package/types/ui/Agent/StateKey.d.ts +16 -0
  267. package/types/ui/Agent/Tool.d.ts +15 -0
  268. package/types/ui/Agent/Transcript.d.ts +13 -0
  269. package/types/ui/Agent/Zone.d.ts +23 -0
  270. package/types/ui/Agent/fetchRunner.d.ts +12 -0
  271. package/types/ui/Agent/index.d.ts +17 -0
  272. package/types/ui/Agent/index_.d.ts +1 -0
  273. package/types/ui/Agent/sessionHistory.d.ts +12 -0
  274. package/types/ui/Agent.d.ts +1 -0
  275. package/types/ui/Badge.d.ts +1 -1
  276. package/types/ui/Button.d.ts +1 -1
  277. package/types/ui/CsrImage.d.ts +1 -1
  278. package/types/ui/Dropdown.d.ts +2 -0
  279. package/types/ui/Image.d.ts +3 -3
  280. package/types/ui/Layout/BottomInset.d.ts +2 -1
  281. package/types/ui/Layout/index.d.ts +1 -1
  282. package/types/ui/Signal/style.d.ts +4 -1
  283. package/types/ui/UiOverride/context.d.ts +14 -2
  284. package/types/ui/agentAttrs.d.ts +14 -0
  285. package/types/ui/index.d.ts +4 -1
  286. package/types/ui/overlayLayer.d.ts +24 -0
  287. package/types/ui/recipe/badgeRecipe.d.ts +7 -3
  288. package/types/ui/recipe/buttonRecipe.d.ts +7 -3
  289. package/types/ui/recipe/inputRecipe.d.ts +5 -1
  290. package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
  291. package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
  292. package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
  293. package/types/vendor/use-agentic/Agentic.d.ts +21 -0
  294. package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
  295. package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
  296. package/types/vendor/use-agentic/index.d.ts +13 -0
  297. package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
  298. package/types/vendor/use-agentic/types.d.ts +133 -0
  299. package/types/vendor/use-agentic/useAgent.d.ts +4 -0
  300. package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
  301. package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
  302. package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
  303. package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
  304. package/types/vendor/use-agentic.d.ts +1 -0
  305. package/types/webkit/index.d.ts +3 -0
  306. package/types/webkit/lazy.d.ts +12 -0
  307. package/types/webkit/useCsrValues.d.ts +3 -3
  308. package/types/webkit/useEscapeKey.d.ts +5 -0
  309. package/types/webkit/useFrameRuntime.d.ts +6 -1
  310. package/types/webkit/useScreenScope.d.ts +18 -0
  311. package/ui/Agent/Approval.tsx +29 -0
  312. package/ui/Agent/Bubble.tsx +74 -0
  313. package/ui/Agent/Chat.tsx +225 -0
  314. package/ui/Agent/Context.tsx +38 -0
  315. package/ui/Agent/Dock.tsx +72 -0
  316. package/ui/Agent/Guide.tsx +16 -0
  317. package/ui/Agent/Section.tsx +24 -0
  318. package/ui/Agent/StateKey.tsx +44 -0
  319. package/ui/Agent/Tool.tsx +66 -0
  320. package/ui/Agent/Transcript.tsx +33 -0
  321. package/ui/Agent/Zone.tsx +48 -0
  322. package/ui/Agent/fetchRunner.ts +30 -0
  323. package/ui/Agent/index.ts +12 -0
  324. package/ui/Agent/index_.tsx +4 -0
  325. package/ui/Agent/sessionHistory.ts +33 -0
  326. package/ui/Badge.tsx +3 -3
  327. package/ui/BottomSheet.tsx +5 -0
  328. package/ui/Button.tsx +6 -2
  329. package/ui/Constant/Doc.tsx +1 -1
  330. package/ui/CsrImage.tsx +1 -1
  331. package/ui/Data/ListContainer.tsx +1 -1
  332. package/ui/DatePicker.tsx +5 -4
  333. package/ui/Dialog/Modal.tsx +12 -15
  334. package/ui/DraggableList.tsx +3 -1
  335. package/ui/Dropdown.tsx +33 -11
  336. package/ui/Field.tsx +30 -16
  337. package/ui/Image.tsx +3 -3
  338. package/ui/Input.tsx +14 -5
  339. package/ui/Layout/BottomInset.tsx +6 -1
  340. package/ui/Load/Units.tsx +11 -2
  341. package/ui/Load/View.tsx +7 -2
  342. package/ui/Loading/ProgressBar.tsx +8 -1
  343. package/ui/Menu.tsx +7 -8
  344. package/ui/Model/EditModal.tsx +37 -2
  345. package/ui/Model/SureToRemove.tsx +2 -1
  346. package/ui/Model/index_.tsx +42 -15
  347. package/ui/ObjectId.tsx +3 -4
  348. package/ui/Pagination.tsx +3 -4
  349. package/ui/Popconfirm.tsx +8 -7
  350. package/ui/Select.tsx +6 -4
  351. package/ui/Signal/RestApi.tsx +43 -16
  352. package/ui/Signal/WebSocket.tsx +1 -1
  353. package/ui/Signal/style.ts +6 -1
  354. package/ui/Switch.tsx +2 -0
  355. package/ui/System/CSR.tsx +6 -2
  356. package/ui/System/ThemeToggle.tsx +55 -33
  357. package/ui/ToggleSelect.tsx +4 -3
  358. package/ui/Tooltip.tsx +2 -1
  359. package/ui/UiOverride/context.ts +14 -2
  360. package/ui/agentAttrs.ts +19 -0
  361. package/ui/index.ts +10 -1
  362. package/ui/overlayLayer.ts +39 -0
  363. package/ui/recipe/badgeRecipe.ts +22 -3
  364. package/ui/recipe/buttonRecipe.ts +51 -2
  365. package/ui/recipe/factory.ts +2 -2
  366. package/ui/recipe/inputRecipe.ts +18 -4
  367. package/vendor/use-agentic/AgentProvider.tsx +39 -0
  368. package/vendor/use-agentic/AgentScope.tsx +20 -0
  369. package/vendor/use-agentic/AgentSession.ts +292 -0
  370. package/vendor/use-agentic/Agentic.tsx +44 -0
  371. package/vendor/use-agentic/AgenticSurface.ts +294 -0
  372. package/vendor/use-agentic/WIRE.md +57 -0
  373. package/vendor/use-agentic/httpRunner.ts +115 -0
  374. package/vendor/use-agentic/index.ts +14 -0
  375. package/vendor/use-agentic/surfaceContext.ts +9 -0
  376. package/vendor/use-agentic/types.ts +144 -0
  377. package/vendor/use-agentic/useAgent.ts +17 -0
  378. package/vendor/use-agentic/useAgentGuide.ts +16 -0
  379. package/vendor/use-agentic/useAgentResource.ts +30 -0
  380. package/vendor/use-agentic/useAgentState.ts +61 -0
  381. package/vendor/use-agentic/useAgentTool.ts +55 -0
  382. package/webkit/bootCsr.tsx +2 -33
  383. package/webkit/index.ts +3 -0
  384. package/webkit/lazy.tsx +27 -3
  385. package/webkit/useCsrValues.ts +121 -4
  386. package/webkit/useEscapeKey.tsx +42 -0
  387. package/webkit/useFrameRuntime.ts +65 -32
  388. package/webkit/useScreenScope.tsx +49 -0
package/ui/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { Agent } from "./Agent";
2
+ export { agentAttrs } from "./agentAttrs";
1
3
  export { animated } from "./animated";
2
4
  export { Badge } from "./Badge";
3
5
  export { BottomSheet, type BottomSheetRef } from "./BottomSheet";
@@ -12,7 +14,7 @@ export { DatePicker } from "./DatePicker";
12
14
  export { Dialog } from "./Dialog";
13
15
  export { DragAction } from "./DragAction";
14
16
  export { DraggableList } from "./DraggableList";
15
- export { Dropdown } from "./Dropdown";
17
+ export { DROPDOWN_KEEP_OPEN_ATTR, Dropdown } from "./Dropdown";
16
18
  export { Empty } from "./Empty";
17
19
  export { Field } from "./Field";
18
20
  export { FontFace } from "./FontFace";
@@ -29,6 +31,13 @@ export { Modal } from "./Modal";
29
31
  export { Model } from "./Model";
30
32
  export { More } from "./More";
31
33
  export { ObjectId } from "./ObjectId";
34
+ export {
35
+ isOwnOverlayClick,
36
+ OVERLAY_LAYER_ATTR,
37
+ OverlayOwnerProvider,
38
+ useOverlayLayerProps,
39
+ useOverlayScope,
40
+ } from "./overlayLayer";
32
41
  export { Pagination } from "./Pagination";
33
42
  export { Popconfirm } from "./Popconfirm";
34
43
  export { Portal } from "./Portal";
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { createContext, useContext } from "react";
3
+
4
+ /**
5
+ * Overlay surfaces render through `createPortal(document.body)`, so they leave the DOM subtree of the
6
+ * component that opened them, and a dismiss check asking `ref.current.contains(target)` reads every
7
+ * click inside a dialog it opened *itself* as an outside click.
8
+ *
9
+ * React context reaches through a portal where the DOM does not, so an overlay can read which
10
+ * dismissable scope rendered it and stamp that owner on the roots it portals out. A scope then
11
+ * recognises its own overlays exactly, instead of guessing from how close a dialog happens to be.
12
+ */
13
+ export const OVERLAY_LAYER_ATTR = "data-akan-overlay";
14
+
15
+ /** Scope that rendered the surrounding content — empty at page level. */
16
+ const OverlayOwnerContext = createContext("");
17
+
18
+ /** Wrap the content a dismissable container owns, with the scope from {@link useOverlayScope}. */
19
+ export const OverlayOwnerProvider = OverlayOwnerContext.Provider;
20
+
21
+ /** Scope a dismissable container hands to its content. Nests as a `parent/child` path. */
22
+ export const useOverlayScope = (id: string) => {
23
+ const parent = useContext(OverlayOwnerContext);
24
+ return parent ? `${parent}/${id}` : id;
25
+ };
26
+
27
+ /** Props an overlay spreads onto every root it portals out of its own tree. */
28
+ export const useOverlayLayerProps = () => ({ [OVERLAY_LAYER_ATTR]: useContext(OverlayOwnerContext) });
29
+
30
+ /**
31
+ * True when the click landed in an overlay that `scope` — or a scope nested inside it — rendered.
32
+ * An overlay nobody owns, and one owned by an unrelated scope, both read as an ordinary outside
33
+ * click, so a menu does not linger behind a dialog it has nothing to do with.
34
+ */
35
+ export const isOwnOverlayClick = (target: EventTarget | null, scope: string) => {
36
+ if (!(target instanceof Element)) return false;
37
+ const owner = target.closest(`[${OVERLAY_LAYER_ATTR}]`)?.getAttribute(OVERLAY_LAYER_ATTR);
38
+ return !!owner && (owner === scope || owner.startsWith(`${scope}/`));
39
+ };
@@ -1,23 +1,42 @@
1
1
  import { recipe, tv } from "./factory";
2
2
 
3
- /** 뱃지 look — 시맨틱 variant. `<Badge>` 가 소비하며, recipes.badge 슬롯으로 교체 가능. */
3
+ /** 뱃지 look — 시맨틱 variant × size, outline 플래그는 색을 유지한 외곽선 스타일. `<Badge>` 가 소비하며, recipes.badge 슬롯으로 교체 가능. */
4
4
  export const badgeRecipe = recipe(
5
5
  tv({
6
- base: "inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 font-medium text-xs",
6
+ base: "inline-flex items-center gap-1 rounded-full border font-medium",
7
7
  variants: {
8
8
  variant: {
9
9
  default: "border-transparent bg-muted text-foreground",
10
10
  primary: "border-transparent bg-primary text-primary-foreground",
11
11
  secondary: "border-transparent bg-secondary text-secondary-foreground",
12
12
  accent: "border-transparent bg-accent text-accent-foreground",
13
+ neutral: "border-transparent bg-neutral text-neutral-foreground",
13
14
  success: "border-transparent bg-success text-success-foreground",
14
15
  warning: "border-transparent bg-warning text-warning-foreground",
15
16
  info: "border-transparent bg-info text-info-foreground",
16
17
  error: "border-transparent bg-destructive text-destructive-foreground",
17
18
  outline: "border-border text-foreground",
18
19
  },
20
+ size: {
21
+ xs: "px-1.5 py-0 text-[0.625rem]",
22
+ sm: "px-2 py-0.5 text-xs",
23
+ md: "px-2.5 py-0.5 text-xs",
24
+ lg: "px-3 py-1 text-sm",
25
+ },
26
+
27
+ outline: { true: "border-current bg-transparent" },
19
28
  },
20
- defaultVariants: { variant: "default" },
29
+ compoundVariants: [
30
+ { variant: "primary", outline: true, class: "text-primary" },
31
+ { variant: "secondary", outline: true, class: "text-secondary" },
32
+ { variant: "accent", outline: true, class: "text-accent" },
33
+ { variant: "neutral", outline: true, class: "text-neutral" },
34
+ { variant: "success", outline: true, class: "text-success" },
35
+ { variant: "warning", outline: true, class: "text-warning" },
36
+ { variant: "info", outline: true, class: "text-info" },
37
+ { variant: "error", outline: true, class: "text-destructive" },
38
+ ],
39
+ defaultVariants: { variant: "default", size: "md" },
21
40
  }),
22
41
  );
23
42
  export type BadgeVariants = NonNullable<Parameters<typeof badgeRecipe>[0]>;
@@ -1,15 +1,18 @@
1
1
  import { recipe, tv } from "./factory";
2
2
 
3
- /** 버튼 look — 시맨틱 variant × size. `<Button>` 이 소비하며, `_overrides.tsx` 의 recipes.button 슬롯으로 교체 가능. */
3
+ /** 버튼 look — 시맨틱 variant × size × shape, outline 플래그는 색을 유지한 외곽선 스타일. `<Button>` 이 소비하며, `_overrides.tsx` 의 recipes.button 슬롯으로 교체 가능. */
4
4
  export const buttonRecipe = recipe(
5
5
  tv({
6
6
 
7
7
  base: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-field font-medium transition active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
8
8
  variants: {
9
9
  variant: {
10
+
11
+ default: "bg-muted text-foreground hover:bg-muted/80",
10
12
  primary: "bg-primary text-primary-foreground hover:bg-primary/90",
11
13
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
12
14
  accent: "bg-accent text-accent-foreground hover:bg-accent/90",
15
+ neutral: "bg-neutral text-neutral-foreground hover:bg-neutral/90",
13
16
  outline: "border border-input bg-background hover:bg-muted hover:text-foreground",
14
17
  ghost: "hover:bg-muted hover:text-foreground",
15
18
  destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
@@ -25,8 +28,54 @@ export const buttonRecipe = recipe(
25
28
  lg: "h-12 px-6 text-lg",
26
29
  icon: "h-10 w-10",
27
30
  },
31
+
32
+ shape: {
33
+ default: "",
34
+ square: "aspect-square px-0",
35
+ circle: "aspect-square rounded-full px-0",
36
+ },
37
+
38
+ outline: { true: "border bg-transparent" },
28
39
  },
29
- defaultVariants: { variant: "primary", size: "md" },
40
+ compoundVariants: [
41
+ {
42
+ variant: "primary",
43
+ outline: true,
44
+ class: "border-primary text-primary hover:bg-primary hover:text-primary-foreground",
45
+ },
46
+ {
47
+ variant: "secondary",
48
+ outline: true,
49
+ class: "border-secondary text-secondary hover:bg-secondary hover:text-secondary-foreground",
50
+ },
51
+ {
52
+ variant: "accent",
53
+ outline: true,
54
+ class: "border-accent text-accent hover:bg-accent hover:text-accent-foreground",
55
+ },
56
+ {
57
+ variant: "neutral",
58
+ outline: true,
59
+ class: "border-neutral text-neutral hover:bg-neutral hover:text-neutral-foreground",
60
+ },
61
+ {
62
+ variant: "destructive",
63
+ outline: true,
64
+ class: "border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground",
65
+ },
66
+ {
67
+ variant: "success",
68
+ outline: true,
69
+ class: "border-success text-success hover:bg-success hover:text-success-foreground",
70
+ },
71
+ {
72
+ variant: "warning",
73
+ outline: true,
74
+ class: "border-warning text-warning hover:bg-warning hover:text-warning-foreground",
75
+ },
76
+ { variant: "info", outline: true, class: "border-info text-info hover:bg-info hover:text-info-foreground" },
77
+ ],
78
+ defaultVariants: { variant: "primary", size: "md", shape: "default" },
30
79
  }),
31
80
  );
32
81
  export type ButtonVariants = NonNullable<Parameters<typeof buttonRecipe>[0]>;
@@ -1,6 +1,6 @@
1
1
  import type { ClassNameValue as ClassValue } from "tailwind-merge";
2
2
  import { createTV } from "tailwind-variants";
3
- import { colorTokens } from "../../client/cn";
3
+ import { colorTokens, radiusTokens } from "../../client/cn";
4
4
 
5
5
  /**
6
6
  * recipe 팩토리 모듈 — 변형 팩토리를 만드는 **인프라**. **서버-안전** (절대 `"use client"` 금지).
@@ -8,7 +8,7 @@ import { colorTokens } from "../../client/cn";
8
8
  * 각 앱은 `apps/<app>/ui/Recipe/<name>.ts`.
9
9
  */
10
10
 
11
- export const tv = createTV({ twMergeConfig: { extend: { theme: { color: colorTokens } } } });
11
+ export const tv = createTV({ twMergeConfig: { extend: { theme: { color: colorTokens, radius: radiusTokens } } } });
12
12
 
13
13
  /**
14
14
  * recipe 팩토리 — `tv({...})` 인스턴스를 받아 겉을 `(변형, className?)` 2-인자로 노출하고,
@@ -1,13 +1,27 @@
1
1
  import { recipe, tv } from "./factory";
2
2
 
3
- /** 입력 표면 look — Input/TextArea 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area) 고른다. */
3
+ /** 입력 표면 look — Input/TextArea/Select 가 공유하는 필드 셸. kind 로 한 줄 필드(field)/멀티라인(area), tone 으로 강조/오류 상태를 고른다. */
4
4
  export const inputRecipe = recipe(
5
5
  tv({
6
- base: "w-full rounded-field border border-input bg-background text-foreground text-sm focus:border-primary focus:outline-none",
6
+ base: "w-full rounded-field border border-input bg-background text-foreground focus:border-primary focus:outline-none",
7
7
  variants: {
8
- kind: { field: "h-10 px-3", area: "p-3" },
8
+ kind: { field: "px-3", area: "p-3" },
9
+ size: { xs: "text-xs", sm: "text-sm", md: "text-sm", lg: "text-base", xl: "text-lg" },
10
+ tone: {
11
+ default: "",
12
+ primary: "border-primary",
13
+ error: "border-destructive focus:border-destructive",
14
+ },
9
15
  },
10
- defaultVariants: { kind: "field" },
16
+
17
+ compoundVariants: [
18
+ { kind: "field", size: "xs", class: "h-6" },
19
+ { kind: "field", size: "sm", class: "h-8" },
20
+ { kind: "field", size: "md", class: "h-10" },
21
+ { kind: "field", size: "lg", class: "h-12" },
22
+ { kind: "field", size: "xl", class: "h-14" },
23
+ ],
24
+ defaultVariants: { kind: "field", size: "md", tone: "default" },
11
25
  }),
12
26
  );
13
27
  export type InputSurfaceVariants = NonNullable<Parameters<typeof inputRecipe>[0]>;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { type ReactNode, useRef } from "react";
3
+ import { AgenticSurface } from "./AgenticSurface";
4
+ import { AgentSession, type AgentSessionOptions } from "./AgentSession";
5
+ import { SurfaceContext } from "./surfaceContext";
6
+ import type { AgentRunner } from "./types";
7
+ import { SessionContext } from "./useAgent";
8
+
9
+ interface AgentProviderProps extends AgentSessionOptions {
10
+ surface?: AgenticSurface;
11
+ session?: AgentSession;
12
+ runner?: AgentRunner;
13
+ children: ReactNode;
14
+ }
15
+
16
+ /**
17
+ * Scopes a subtree onto one surface and, when a runner or session is given, one conversation.
18
+ *
19
+ * Without a provider, hooks land on `AgenticSurface.shared` — a provider exists to isolate, or to hold the session
20
+ * `useAgent` reads.
21
+ */
22
+ export const AgentProvider = ({ surface, session, runner, children, ...options }: AgentProviderProps) => {
23
+ const held = useRef<{ surface: AgenticSurface; session: AgentSession | null } | null>(null);
24
+ if (!held.current) {
25
+ const sessionSurface = session?.surface;
26
+
27
+ const heldSurface =
28
+ surface ?? (sessionSurface instanceof AgenticSurface ? sessionSurface : undefined) ?? new AgenticSurface();
29
+ held.current = {
30
+ surface: heldSurface,
31
+ session: session ?? (runner ? new AgentSession(heldSurface, runner, options) : null),
32
+ };
33
+ }
34
+ return (
35
+ <SurfaceContext.Provider value={held.current.surface}>
36
+ <SessionContext.Provider value={held.current.session}>{children}</SessionContext.Provider>
37
+ </SurfaceContext.Provider>
38
+ );
39
+ };
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { type ReactNode, useEffect, useMemo } from "react";
3
+ import { AgenticSurface } from "./AgenticSurface";
4
+ import { ScopeContext, useScopePath, useSurface } from "./surfaceContext";
5
+
6
+ export interface AgentScopeProps {
7
+ id: string;
8
+ label?: string;
9
+ kind?: string;
10
+ children: ReactNode;
11
+ }
12
+
13
+ /** Namespaces every tool and resource registered below it, so list items can reuse local names. */
14
+ export const AgentScope = ({ id, label, kind, children }: AgentScopeProps) => {
15
+ const surface = useSurface();
16
+ const parent = useScopePath();
17
+ const path = useMemo(() => AgenticSurface.childPath(parent, id), [parent, id]);
18
+ useEffect(() => surface.openScope(parent, { id, label, kind }), [surface, path.join("."), label, kind]);
19
+ return <ScopeContext.Provider value={path}>{children}</ScopeContext.Provider>;
20
+ };
@@ -0,0 +1,292 @@
1
+ import type {
2
+ AgentRunner,
3
+ ChatMessage,
4
+ ContextBlock,
5
+ RunnerRequest,
6
+ SurfaceView,
7
+ ToolCallRequest,
8
+ ToolCallResult,
9
+ ToolEntry,
10
+ } from "./types";
11
+
12
+ export interface PendingApproval {
13
+ callId: string;
14
+ name: string;
15
+ args: Record<string, unknown>;
16
+ message: string;
17
+ approve: () => void;
18
+ reject: (reason?: string) => void;
19
+ }
20
+
21
+ /** Where a session keeps its transcript across page loads. Storage-neutral: the host decides what backs it. */
22
+ export interface SessionHistory {
23
+ load(): ChatMessage[] | null;
24
+ save(messages: readonly ChatMessage[]): void;
25
+ clear(): void;
26
+ }
27
+
28
+ export interface AgentSessionOptions {
29
+ instructions?: string;
30
+ buildContext?: (surface: SurfaceView) => ContextBlock[];
31
+ /** Assistant turns per send before the session stops the loop. */
32
+ maxTurns?: number;
33
+ /** Restores settled messages at construction and saves after every change, debounced. Failures are silent. */
34
+ history?: SessionHistory;
35
+ }
36
+
37
+ /**
38
+ * The client-side conversation loop: send → model turn → tool calls → approval gate → execute → report diffs → next
39
+ * turn. The loop lives here rather than on a server because the tools do — the runner is one stateless model turn,
40
+ * so any backend that answers it works and none has to hold a session.
41
+ *
42
+ * Failures land in the transcript instead of being thrown past it: a refused guard, a rejected approval, and an
43
+ * unknown tool are all things the agent did, and the model reads them as tool results the same way a person reads
44
+ * them in the chat.
45
+ */
46
+ export class AgentSession {
47
+ readonly #surface: SurfaceView;
48
+ readonly #runner: AgentRunner;
49
+ readonly #options: AgentSessionOptions;
50
+ #messages: ChatMessage[] = [];
51
+ #running = false;
52
+ #pending: PendingApproval | null = null;
53
+ #controller: AbortController | null = null;
54
+ #version = 0;
55
+ #listeners = new Set<() => void>();
56
+ #saveTimer: ReturnType<typeof setTimeout> | null = null;
57
+
58
+ constructor(surface: SurfaceView, runner: AgentRunner, options: AgentSessionOptions = {}) {
59
+ this.#surface = surface;
60
+ this.#runner = runner;
61
+ this.#options = options;
62
+ this.#messages = AgentSession.#restored(options.history);
63
+ }
64
+
65
+ get surface(): SurfaceView {
66
+ return this.#surface;
67
+ }
68
+
69
+ get messages(): readonly ChatMessage[] {
70
+ return this.#messages;
71
+ }
72
+
73
+ get isRunning() {
74
+ return this.#running;
75
+ }
76
+
77
+ get pendingApproval(): PendingApproval | null {
78
+ return this.#pending;
79
+ }
80
+
81
+ /** Bumped on every change, so a store binding can use it as the snapshot. */
82
+ get version() {
83
+ return this.#version;
84
+ }
85
+
86
+ subscribe = (listener: () => void) => {
87
+ this.#listeners.add(listener);
88
+ return () => {
89
+ this.#listeners.delete(listener);
90
+ };
91
+ };
92
+
93
+ /** A user's text, or prewritten messages — how a host injects a prompt's result as the user's turn. */
94
+ send = async (input: string | ChatMessage[]) => {
95
+ if (this.#running) throw new Error("A turn is already running.");
96
+ this.#running = true;
97
+ const controller = new AbortController();
98
+ this.#controller = controller;
99
+ if (typeof input === "string") this.#append({ role: "user", text: input });
100
+ else for (const message of input) this.#append(message);
101
+ const maxTurns = this.#options.maxTurns ?? 8;
102
+ try {
103
+ for (let turn = 0; turn < maxTurns; turn += 1) {
104
+ if (controller.signal.aborted) return;
105
+ const { toolCalls, stop } = await this.#assistantTurn(controller.signal);
106
+ if (controller.signal.aborted || stop !== "toolUse" || !toolCalls.length) return;
107
+ const toolResults: ToolCallResult[] = [];
108
+ for (const call of toolCalls) {
109
+ if (controller.signal.aborted) break;
110
+ toolResults.push(await this.#execute(call, controller.signal));
111
+ }
112
+ this.#append({ role: "tool", toolResults });
113
+ }
114
+ this.#fail(`Stopped after ${maxTurns} assistant turns without a final answer.`);
115
+ } catch (error) {
116
+ if (!controller.signal.aborted) this.#fail(error instanceof Error ? error.message : String(error));
117
+ } finally {
118
+ this.#running = false;
119
+ this.#controller = null;
120
+ this.#pending = null;
121
+ this.#notify();
122
+ }
123
+ };
124
+
125
+ abort = () => {
126
+ this.#controller?.abort();
127
+ };
128
+
129
+ /** Empties the transcript and the persisted history. A running turn keeps its transcript. */
130
+ reset = () => {
131
+ if (this.#running) return;
132
+ this.#messages = [];
133
+
134
+ if (this.#saveTimer) {
135
+ clearTimeout(this.#saveTimer);
136
+ this.#saveTimer = null;
137
+ }
138
+ try {
139
+ this.#options.history?.clear();
140
+ } catch {
141
+ }
142
+ this.#version += 1;
143
+ for (const listener of this.#listeners) listener();
144
+ };
145
+
146
+ /** Records a host-side failure (a prompt fetch, an upload) in the transcript, where every other failure lands. */
147
+ report = (error: string) => {
148
+ this.#append({ role: "assistant", error });
149
+ };
150
+
151
+ async #assistantTurn(signal: AbortSignal): Promise<{ toolCalls: ToolCallRequest[]; stop: "end" | "toolUse" }> {
152
+ const { tools, guides } = this.#surface.snapshot();
153
+ const instructions = [this.#options.instructions, ...guides].filter(Boolean).join("\n\n");
154
+ const request: RunnerRequest = {
155
+ messages: this.#messages,
156
+ tools,
157
+ context: this.#options.buildContext?.(this.#surface) ?? AgentSession.#defaultContext(this.#surface),
158
+ ...(instructions ? { instructions } : {}),
159
+ signal,
160
+ };
161
+ this.#append({ role: "assistant" });
162
+ let text = "";
163
+ const toolCalls: ToolCallRequest[] = [];
164
+ let stop: "end" | "toolUse" = "end";
165
+ for await (const event of this.#runner.run(request)) {
166
+ if (signal.aborted) break;
167
+ if (event.type === "text") {
168
+ text += event.delta;
169
+ this.#patchLast({ text });
170
+ } else if (event.type === "toolCall") toolCalls.push({ id: event.id, name: event.name, args: event.args });
171
+ else if (event.type === "done") stop = event.stop;
172
+ else throw new Error(event.message);
173
+ }
174
+ if (toolCalls.length) this.#patchLast({ toolCalls });
175
+ return { toolCalls, stop };
176
+ }
177
+
178
+ async #execute(call: ToolCallRequest, signal: AbortSignal): Promise<ToolCallResult> {
179
+ const base = { id: call.id, name: call.name };
180
+ const entry = this.#surface.tool(call.name);
181
+ if (!entry) return { ...base, error: `Unknown tool: ${call.name}` };
182
+ const message = AgentSession.#confirmMessage(call.name, entry, call.args);
183
+ if (message) {
184
+ const approved = await this.#awaitApproval(call, message, signal);
185
+ if (approved !== true) return { ...base, error: approved };
186
+ }
187
+ const before = this.#surface.snapshot();
188
+ try {
189
+ const result = await this.#surface.call(call.name, call.args);
190
+ const changes = this.#surface.diffSince(before);
191
+ return {
192
+ ...base,
193
+ ...(result !== undefined ? { result } : {}),
194
+ ...(changes.length ? { changes } : {}),
195
+ };
196
+ } catch (error) {
197
+ return { ...base, error: error instanceof Error ? error.message : String(error) };
198
+ }
199
+ }
200
+
201
+ #awaitApproval(call: ToolCallRequest, message: string, signal: AbortSignal): Promise<true | string> {
202
+ return new Promise((resolve) => {
203
+ const settle = (value: true | string) => {
204
+ this.#pending = null;
205
+ signal.removeEventListener("abort", onAbort);
206
+ this.#notify();
207
+ resolve(value);
208
+ };
209
+ const onAbort = () => settle("The user aborted the turn.");
210
+ signal.addEventListener("abort", onAbort);
211
+ this.#pending = {
212
+ callId: call.id,
213
+ name: call.name,
214
+ args: call.args,
215
+ message,
216
+ approve: () => settle(true),
217
+ reject: (reason) => settle(reason ?? "The user declined."),
218
+ };
219
+ this.#notify();
220
+ });
221
+ }
222
+
223
+ static #confirmMessage(name: string, entry: ToolEntry, args: Record<string, unknown>): string | null {
224
+ const confirm = entry.confirm;
225
+ if (confirm === undefined || confirm === false) return null;
226
+ if (typeof confirm === "string") return confirm;
227
+ const verdict = confirm === true ? true : confirm(args);
228
+ if (verdict === false) return null;
229
+ return verdict === true ? `Run ${name}?` : verdict;
230
+ }
231
+
232
+ static #defaultContext(surface: SurfaceView): ContextBlock[] {
233
+ const { resources, scopes } = surface.snapshot();
234
+ return [
235
+ ...(scopes.length ? [{ kind: "screen", scopes }] : []),
236
+ ...(resources.length ? [{ kind: "resources", resources }] : []),
237
+ ];
238
+ }
239
+
240
+ /** Recorded on the open assistant draft when there is one, so a failed turn reads as that turn failing. */
241
+ #fail(message: string) {
242
+ const last = this.#messages[this.#messages.length - 1];
243
+ if (last?.role === "assistant" && !last.error) this.#patchLast({ error: message });
244
+ else this.#append({ role: "assistant", error: message });
245
+ }
246
+
247
+ #append(message: ChatMessage) {
248
+ this.#messages = [...this.#messages, message];
249
+ this.#notify();
250
+ }
251
+
252
+ #patchLast(patch: Partial<ChatMessage>) {
253
+ const last = this.#messages[this.#messages.length - 1];
254
+ if (!last) return;
255
+ this.#messages = [...this.#messages.slice(0, -1), { ...last, ...patch }];
256
+ this.#notify();
257
+ }
258
+
259
+ #notify() {
260
+ this.#version += 1;
261
+ for (const listener of this.#listeners) listener();
262
+ this.#schedulePersist();
263
+ }
264
+
265
+ /** Debounced: streaming patches the last message on every delta, and a save per delta would thrash storage. */
266
+ #schedulePersist() {
267
+ const history = this.#options.history;
268
+ if (!history) return;
269
+ if (this.#saveTimer) clearTimeout(this.#saveTimer);
270
+ this.#saveTimer = setTimeout(() => {
271
+ this.#saveTimer = null;
272
+ try {
273
+ history.save(this.#messages);
274
+ } catch {
275
+ }
276
+ }, 300);
277
+ }
278
+
279
+ static #restored(history: SessionHistory | undefined): ChatMessage[] {
280
+ if (!history) return [];
281
+ try {
282
+ const messages = history.load();
283
+ if (!Array.isArray(messages)) return [];
284
+
285
+ return messages.filter(
286
+ (message) => message.role !== "assistant" || !!message.text || !!message.toolCalls?.length || !!message.error,
287
+ );
288
+ } catch {
289
+ return [];
290
+ }
291
+ }
292
+ }
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { Children, type ReactElement, useEffect, useRef } from "react";
3
+ import { useScopePath, useSurface } from "./surfaceContext";
4
+ import type { ToolConfirm, ToolEffect } from "./types";
5
+
6
+ interface AgenticProps {
7
+ name: string;
8
+ description?: string;
9
+ effect?: ToolEffect;
10
+ confirm?: ToolConfirm;
11
+ children: ReactElement<{ onClick?: () => unknown }>;
12
+ }
13
+
14
+ /**
15
+ * Registers the child's `onClick` as a no-argument tool and renders the child untouched.
16
+ *
17
+ * The first-class path is passing a hook-made tool by reference as the handler itself; this wrapper is for
18
+ * elements whose handler you do not own — third-party components and embeds.
19
+ */
20
+ export const Agentic = ({ name, description, effect, confirm, children }: AgenticProps) => {
21
+ const surface = useSurface();
22
+ const scope = useScopePath();
23
+ const child = Children.only(children);
24
+ const live = useRef({ onClick: child.props.onClick, confirm });
25
+ live.current = { onClick: child.props.onClick, confirm };
26
+ const scopeKey = scope.join(".");
27
+ useEffect(() => {
28
+ return surface.registerTool(scope, {
29
+ name,
30
+ description,
31
+ effect,
32
+ ...(confirm === undefined
33
+ ? {}
34
+ : {
35
+ confirm: (args: Record<string, unknown>) => {
36
+ const latest = live.current.confirm;
37
+ return typeof latest === "function" ? latest(args) : (latest ?? false);
38
+ },
39
+ }),
40
+ run: () => live.current.onClick?.(),
41
+ });
42
+ }, [surface, scopeKey, name]);
43
+ return children;
44
+ };