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/signal/slice.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { Any, type Assign, type Cls, type MergeAllKeyOfObjects, SLICE_DICT_SHAPE, SLICE_META } from "akanjs/base";
1
+ import { Any, type Assign, type MergeAllKeyOfObjects, SLICE_DICT_SHAPE, SLICE_META } from "akanjs/base";
2
2
  import { applyMixins } from "akanjs/common";
3
3
  import type { DocumentModel, QueryOf } from "akanjs/constant";
4
4
  import type { FilterInstance } from "akanjs/document";
5
5
  import { type Adaptor, type AdaptorCls, dangerouslyAdapt, type ServiceModel } from "akanjs/service";
6
- import type { Guard, GuardCls } from "./guard";
6
+ import type { GuardCls } from "./guard";
7
7
  import {
8
8
  buildSlice,
9
9
  type SliceBuilder,
@@ -42,12 +42,12 @@ export type SliceCls<
42
42
 
43
43
  interface RootSliceOption {
44
44
  guards?: {
45
- root?: Cls<Guard> | Cls<Guard>[];
46
- get?: Cls<Guard> | Cls<Guard>[];
47
- cru?: Cls<Guard> | Cls<Guard>[];
48
- create?: Cls<Guard> | Cls<Guard>[];
49
- update?: Cls<Guard> | Cls<Guard>[];
50
- remove?: Cls<Guard> | Cls<Guard>[];
45
+ root?: GuardCls | GuardCls[];
46
+ get?: GuardCls | GuardCls[];
47
+ cru?: GuardCls | GuardCls[];
48
+ create?: GuardCls | GuardCls[];
49
+ update?: GuardCls | GuardCls[];
50
+ remove?: GuardCls | GuardCls[];
51
51
  };
52
52
  prefix?: string;
53
53
  }
@@ -129,7 +129,7 @@ export function slice<
129
129
  srv.cnst.insight,
130
130
  srv.db.filter,
131
131
  );
132
- const toGuards = (guard?: Cls<Guard> | Cls<Guard>[]) => (guard ? (Array.isArray(guard) ? guard : [guard]) : []);
132
+ const toGuards = (guard?: GuardCls | GuardCls[]) => (guard ? (Array.isArray(guard) ? guard : [guard]) : []);
133
133
  const rootGuards = toGuards(option.guards?.root);
134
134
  const getGuards = toGuards(option.guards?.get);
135
135
  const cruGuards = toGuards(option.guards?.cru);
package/signal/types.ts CHANGED
@@ -114,7 +114,7 @@ export interface SerializedArg {
114
114
  enum?: string;
115
115
  }
116
116
  export interface SerializedEndpoint extends SerializedSignalOption {
117
- type: "query" | "mutation" | "pubsub" | "message";
117
+ type: "query" | "mutation" | "pubsub" | "message" | "prompt";
118
118
  returns: SerializedReturns;
119
119
  }
120
120
  export interface SerializedFilter {
package/store/action.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  type FetchPolicy,
6
6
  isQueryEqual,
7
7
  Logger,
8
- lowerlize,
9
8
  pathSet,
10
9
  resolveFileUploadCapability,
11
10
  } from "akanjs/common";
@@ -33,18 +32,12 @@ import type {
33
32
  SlceDbSort,
34
33
  SliceCls,
35
34
  } from "akanjs/signal";
35
+ import { tagAction } from "./actionTag";
36
+ import { formSetterNames } from "./formSetterNames";
37
+ import type { SliceActionKey } from "./sliceRole";
36
38
  import type { SliceStateKey } from "./state";
37
39
  import type { SetGet, StoreSliceArgs, StoreSliceMap, StoreSliceSuffixCap } from "./types";
38
40
 
39
- type SliceActionKey =
40
- | "initModel"
41
- | "refreshModel"
42
- | "selectModel"
43
- | "setPageOfModel"
44
- | "addPageOfModel"
45
- | "setLimitOfModel"
46
- | "setQueryArgsOfModel"
47
- | "setSortOfModel";
48
41
  type _SliceMap<S extends SliceCls> = StoreSliceMap<S>;
49
42
  type _ActionRefName<S extends SliceCls> = SlceCnstRefName<S>;
50
43
  type _ActionCap<S extends SliceCls> = SlceCnstCapitalizedRefName<S>;
@@ -311,16 +304,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
311
304
  },
312
305
  };
313
306
  const fieldSetAction = Object.entries(modelRef[FIELD_META]).reduce((acc, [key, field]) => {
314
- const [fieldKeyName, classKeyName] = [lowerlize(key), capitalize(key)];
315
- const namesOfField = {
316
- field: fieldKeyName,
317
- Field: classKeyName,
318
- setFieldOnModel: `set${classKeyName}On${className}`,
319
- addFieldOnModel: `add${classKeyName}On${className}`,
320
- subFieldOnModel: `sub${classKeyName}On${className}`,
321
- addOrSubFieldOnModel: `addOrSub${classKeyName}On${className}`,
322
- uploadFieldOnModel: `upload${classKeyName}On${className}`,
323
- };
307
+ const namesOfField = formSetterNames(className, key);
324
308
  const singleFieldSetAction = {
325
309
  [namesOfField.setFieldOnModel]: function (this: SetGet, value: any | null) {
326
310
  this.set((state: { [key: string]: any }) => {
@@ -374,7 +358,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
374
358
  },
375
359
  }
376
360
  : {}),
377
- ...(field.isClass && !!fileUploadRefName && ConstantRegistry.getRefName(field.modelRef) === fileUploadRefName
361
+ ...(field.isClass && fileUploadRefName && ConstantRegistry.getRefName(field.modelRef) === fileUploadRefName
378
362
  ? {
379
363
  [namesOfField.uploadFieldOnModel]: async function (
380
364
  this: SetGet,
@@ -427,6 +411,11 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
427
411
  }
428
412
  : {}),
429
413
  };
414
+
415
+ tagAction(singleFieldSetAction[namesOfField.setFieldOnModel] as (...args: never[]) => unknown, {
416
+ action: namesOfField.setFieldOnModel,
417
+ state: `${names.modelForm}.${namesOfField.field}`,
418
+ });
430
419
  return Object.assign(acc, singleFieldSetAction);
431
420
  }, {});
432
421
  return Object.assign(fieldSetAction, baseSetAction);
@@ -751,7 +740,7 @@ export const makeActions = (refName: string, slice: { [key: string]: SerializedS
751
740
  const id = typeof modelOrId === "string" ? modelOrId : modelOrId.id;
752
741
  this.set({ [names.modelFormLoading]: id, [names.modelModal]: modal ?? "edit" });
753
742
  const model = await (fetch[names.model] as (...args: any[]) => Promise<Full>)(id, { onError });
754
- const modelForm = deepObjectify<Input>(model as unknown as Input);
743
+ const modelForm = immerify(modelRef, deepObjectify<Input>(model as unknown as Input) as object) as Input;
755
744
  this.set({
756
745
  [names.model]: model,
757
746
  [names.modelFormLoading]: false,
@@ -0,0 +1,33 @@
1
+ import { ACTION_TAG } from "akanjs/base";
2
+
3
+ /** Which module declared an action. The dictionary node its words live in is named after it. */
4
+ export interface ActionOwner {
5
+ refName: string;
6
+ }
7
+
8
+ export interface ActionTag {
9
+ /** The `st.do` key this function is. */
10
+ action: string;
11
+ /** The state path it writes, when it writes exactly one — `userForm.name` for a field setter. */
12
+ state?: string;
13
+ }
14
+
15
+ /**
16
+ * Marks a dispatcher with what it does, so a component handed one by reference can say so in the DOM.
17
+ *
18
+ * `onChange={st.do.setNameOnUser}` is the house form for every model field, which means the component already holds
19
+ * everything an annotation needs — it just has no way to read it off a function. This is that way, and it is why
20
+ * `data-akan-*` costs an app no code at all: nobody writes the attribute, the setter carries its own name.
21
+ *
22
+ * Non-enumerable, so it survives neither `{...fn}` nor `JSON.stringify` and shows up in no spread.
23
+ */
24
+ export const tagAction = <T extends (...args: never[]) => unknown>(fn: T, tag: ActionTag): T => {
25
+ Object.defineProperty(fn, ACTION_TAG, { value: tag, configurable: true });
26
+ return fn;
27
+ };
28
+
29
+ export const actionTagOf = (value: unknown): ActionTag | undefined => {
30
+ if (typeof value !== "function") return undefined;
31
+ const tag = (value as unknown as { [key: symbol]: unknown })[ACTION_TAG];
32
+ return tag && typeof tag === "object" ? (tag as ActionTag) : undefined;
33
+ };
@@ -0,0 +1,323 @@
1
+ import { DataList } from "akanjs/base";
2
+ import { Translator } from "akanjs/client";
3
+ import { parseAkanI18nEnv } from "akanjs/common";
4
+ import { ConstantRegistry, type MaskModel, mask } from "akanjs/constant";
5
+ import { FetchClient } from "akanjs/fetch";
6
+ import type { AgentRefusal, JsonSchema, SerializedArg, SerializedSignal } from "akanjs/signal";
7
+
8
+ import { JsonSchemaBuilder } from "../../signal/schema/JsonSchemaBuilder";
9
+ import type { StoreInstance } from "../storeInstance";
10
+ import { StoreRegistry } from "../storeRegistry";
11
+ import type { AgentVisibility } from "./AgentVisibility";
12
+ import { StoreCatalogue } from "./StoreCatalogue";
13
+ import type { SerializedStore, SerializedStoreAction, SerializedStoreState, StoreActionEffect } from "./types";
14
+
15
+ export interface AgentTool {
16
+ name: string;
17
+ title?: string;
18
+ description?: string;
19
+ /** One flat named object, the shape MCP publishes. The bridge maps it onto the action's positional parameters. */
20
+ inputSchema: JsonSchema;
21
+ effect: StoreActionEffect;
22
+ }
23
+
24
+ /** One call the agent made, in the order it made them. */
25
+ export interface AgentCall {
26
+ name: string;
27
+ args: Record<string, unknown>;
28
+ at: Date;
29
+ error?: string;
30
+ }
31
+
32
+ export interface AgentBridgeOptions {
33
+ /** Resolves a dictionary key to its text. Defaults to the seeded `Translator` in the active locale. */
34
+ resolveDescription?: (key: string) => string | undefined;
35
+ }
36
+
37
+ /**
38
+ * What an in-page agent may do to the app the user is looking at.
39
+ *
40
+ * Every call goes through `st.do`, which is the same single dispatch point a click goes through — so the agent
41
+ * cannot reach past what the UI already lets this user do, the app re-renders from the write, and the user watches
42
+ * the result rather than being told about it. That is why the exposure default here is the opposite of the MCP
43
+ * catalogue's: an external agent's `tools/list` is an attack surface built out of names the operator never chose to
44
+ * publish, while this one is the user's own session, under their own credential, with them watching.
45
+ *
46
+ * It is deliberately not an agent. There is no model, no provider, and no key here — an app wires whichever it uses
47
+ * to `tools`, `call`, and `read`. The framework's half is the catalogue, the argument checking, the masking, and the
48
+ * transcript; the conversation is the app's.
49
+ */
50
+ export class AgentBridge {
51
+ readonly refusals: AgentRefusal[];
52
+
53
+ readonly #instance: StoreInstance;
54
+ readonly #store: SerializedStore;
55
+ readonly #options: AgentBridgeOptions;
56
+ readonly #schema = new JsonSchemaBuilder({ refPrefix: "#/$defs/" });
57
+ readonly #byName = new Map<string, SerializedStoreAction>();
58
+ readonly #calls: AgentCall[] = [];
59
+ readonly #visibility: AgentVisibility;
60
+ readonly #allTools: AgentTool[];
61
+ readonly #liveTools = new Map<string, { signature: string; tools: AgentTool[] }>();
62
+
63
+ /**
64
+ * The bridge for the app running in this process: the one store every `st.do` goes through, and every signal any
65
+ * client has applied. An app needs no arguments to reach its own agent surface.
66
+ */
67
+ static of(options: AgentBridgeOptions = {}) {
68
+ return new AgentBridge(StoreRegistry.instance, FetchClient.sharedSerializedSignal, options);
69
+ }
70
+
71
+ constructor(
72
+ instance: StoreInstance,
73
+ serializedSignal: Record<string, SerializedSignal>,
74
+ options: AgentBridgeOptions = {},
75
+ ) {
76
+ this.#instance = instance;
77
+ this.#options = options;
78
+ const catalogue = new StoreCatalogue(instance, serializedSignal);
79
+ this.#store = catalogue.store;
80
+ this.refusals = catalogue.refusals;
81
+ this.#visibility = catalogue.visibility;
82
+ for (const [name, action] of Object.entries(this.#store.action)) this.#byName.set(name, action);
83
+ this.#allTools = Object.entries(this.#store.action).map(([name, action]) => this.#tool(name, action));
84
+ }
85
+
86
+ /**
87
+ * The catalogued actions of the stores the rendered screen is reading right now — the live view, recomputed as
88
+ * components mount and unmount. Global exposure was the first cut's behavior and it published levers the screen
89
+ * does not respond to; the screen's own subscriptions are the honest boundary.
90
+ */
91
+ get tools(): AgentTool[] {
92
+ return this.toolsFor("");
93
+ }
94
+
95
+ /** The live view one zone session reads: owners counted from the keys that zone's own subtree subscribes. */
96
+ toolsFor(viewKey: string): AgentTool[] {
97
+ const live = this.#liveOwners(viewKey);
98
+ const signature = [...live].sort().join(",");
99
+ const cached = this.#liveTools.get(viewKey);
100
+ if (cached && cached.signature === signature) return cached.tools;
101
+ const tools = this.#allTools.filter((tool) => this.#ownerLive(tool.name, live));
102
+ this.#liveTools.set(viewKey, { signature, tools });
103
+ return tools;
104
+ }
105
+
106
+ get visibility(): AgentVisibility {
107
+ return this.#visibility;
108
+ }
109
+
110
+ #liveOwners(viewKey = "") {
111
+ return this.#visibility.liveOwners(this.#instance.liveKeysIn(viewKey));
112
+ }
113
+
114
+ #ownerLive(name: string, live: ReadonlySet<string>) {
115
+ const owner = this.#instance.actionOwners.get(name)?.refName;
116
+ return !owner || live.has(owner);
117
+ }
118
+
119
+ get state(): { [key: string]: SerializedStoreState } {
120
+ return this.#store.state;
121
+ }
122
+
123
+ get transcript(): readonly AgentCall[] {
124
+ return this.#calls;
125
+ }
126
+
127
+ subscribe(listener: () => void) {
128
+ return this.#instance.subscribe(listener);
129
+ }
130
+
131
+ /**
132
+ * The value behind a state key, stripped of what the model marks `hidden` or `secret`.
133
+ *
134
+ * Masking is not optional here even though the data mostly came from the server already masked: `<model>Form`
135
+ * holds what the *user* typed, credentials included, and an in-page agent ships what it reads to a remote model.
136
+ * The mask is by the declared model rather than by the value's class, because `immerify` copies a form into a
137
+ * plain object and the class is gone by the time anyone can ask.
138
+ */
139
+ read(key: string, viewKey = ""): unknown {
140
+ const entry = this.#store.state[key];
141
+ if (!entry) throw new Error(`Unknown state key: ${key}`);
142
+ const owner = this.#visibility.stateOwner(key);
143
+ if (owner && !this.#liveOwners(viewKey).has(owner))
144
+ throw new Error(`State key "${key}" is not part of the current screen's surface.`);
145
+ const value = AgentBridge.#unwrap(this.#instance.get()[key]);
146
+ if (entry.refName && entry.modelType) {
147
+ const model = ConstantRegistry.getModelRef(entry.refName, entry.modelType) as MaskModel;
148
+ return mask(model, value);
149
+ }
150
+ if (AgentBridge.#isPlainValue(value)) return value;
151
+ throw new Error(
152
+ `State key "${key}" holds an object that belongs to no model, so there is nothing to mask it by and it is not published. Read the model's own keys instead.`,
153
+ );
154
+ }
155
+
156
+ /**
157
+ * Dispatches through `st.do`, so what happens is what happens when the user clicks.
158
+ *
159
+ * Arguments arrive named and are mapped onto the action's parameters in declared order. An omitted optional one
160
+ * becomes `null`, which is what the slice query builders already expect; an omitted required one is refused,
161
+ * because the alternative is a call that writes `undefined` into state and reports success.
162
+ */
163
+ async call(name: string, args: Record<string, unknown> = {}, viewKey = "") {
164
+ const action = this.#byName.get(name);
165
+ if (!action) throw new Error(`Unknown action: ${name}`);
166
+
167
+ const record: AgentCall = { name, args, at: new Date() };
168
+ this.#calls.push(record);
169
+ try {
170
+ if (!this.#ownerLive(name, this.#liveOwners(viewKey)))
171
+ throw new Error(`Action "${name}" is not part of the current screen's surface.`);
172
+ const positional = action.args.map((arg) => this.#value(name, arg, args));
173
+ await this.#instance.do[name]?.(...positional);
174
+ } catch (error) {
175
+ record.error = error instanceof Error ? error.message : String(error);
176
+ throw error;
177
+ }
178
+ }
179
+
180
+ #value(name: string, arg: SerializedArg, args: Record<string, unknown>) {
181
+ if (!(arg.name in args) || args[arg.name] === undefined) {
182
+ if (arg.nullable || arg.type === "search") return null;
183
+ throw new Error(`Missing argument "${arg.name}" for ${name}.`);
184
+ }
185
+ return AgentBridge.#checked(name, arg, args[arg.name]);
186
+ }
187
+
188
+ /**
189
+ * Checks a value against what the argument declared, one level of array included.
190
+ *
191
+ * `st.do` accepts anything — it is a rest wrapper — so without this a string where an `Int` belongs is written
192
+ * into state and rendered, and the agent is told the call succeeded. A model argument is checked only for being
193
+ * an object: the published schema describes its fields and the endpoint validates them server-side.
194
+ */
195
+ static #checked(name: string, arg: SerializedArg, value: unknown): unknown {
196
+ const depth = arg.arrDepth ?? 0;
197
+ if (depth) {
198
+ if (!Array.isArray(value)) throw new Error(`Argument "${arg.name}" of ${name} must be an array.`);
199
+ return value.map((item) => AgentBridge.#checked(name, { ...arg, arrDepth: depth - 1 }, item));
200
+ }
201
+ if (value === null) return null;
202
+ if (arg.enum) return AgentBridge.#checkedEnum(name, arg, value);
203
+ switch (arg.refName) {
204
+ case "String":
205
+ case "ID":
206
+ return AgentBridge.#assertType(name, arg, value, "string");
207
+ case "Int":
208
+ if (!Number.isInteger(value)) throw new Error(`Argument "${arg.name}" of ${name} must be a whole number.`);
209
+ return value;
210
+ case "Float":
211
+ if (typeof value !== "number" || !Number.isFinite(value))
212
+ throw new Error(`Argument "${arg.name}" of ${name} must be a finite number.`);
213
+ return value;
214
+ case "Boolean":
215
+ return AgentBridge.#assertType(name, arg, value, "boolean");
216
+ case "Date":
217
+ return AgentBridge.#checkedDate(name, arg, value);
218
+ default:
219
+ if (typeof value !== "object") throw new Error(`Argument "${arg.name}" of ${name} must be an object.`);
220
+ return value;
221
+ }
222
+ }
223
+
224
+ static #assertType(name: string, arg: SerializedArg, value: unknown, type: "string" | "boolean") {
225
+ if (typeof value !== type) throw new Error(`Argument "${arg.name}" of ${name} must be a ${type}.`);
226
+ return value;
227
+ }
228
+
229
+ static #checkedEnum(name: string, arg: SerializedArg, value: unknown) {
230
+ const values = arg.enum ? ConstantRegistry.enum.get(arg.enum)?.values : undefined;
231
+ if (!values) return value;
232
+ if (!values.includes(value as never))
233
+ throw new Error(`Argument "${arg.name}" of ${name} must be one of: ${[...values].join(", ")}.`);
234
+ return value;
235
+ }
236
+
237
+ /** An agent has no `Date`, so an ISO string is what it sends. Anything unparseable is refused rather than `Invalid Date`. */
238
+ static #checkedDate(name: string, arg: SerializedArg, value: unknown) {
239
+ if (value instanceof Date) return value;
240
+ const parsed = typeof value === "string" ? new Date(value) : null;
241
+ if (!parsed || Number.isNaN(parsed.getTime()))
242
+ throw new Error(`Argument "${arg.name}" of ${name} must be an ISO 8601 date string.`);
243
+ return parsed;
244
+ }
245
+
246
+ #tool(name: string, action: SerializedStoreAction): AgentTool {
247
+ const properties = Object.fromEntries(action.args.map((arg) => [arg.name, this.#argSchema(action, arg)]));
248
+ const required = action.args.filter((arg) => !arg.nullable && arg.type !== "search").map((arg) => arg.name);
249
+ const defs = this.#schema.referencedSchemas(properties);
250
+ return {
251
+ name,
252
+ ...this.#texts(action),
253
+ inputSchema: {
254
+ type: "object",
255
+ properties,
256
+ ...(required.length ? { required } : {}),
257
+ additionalProperties: false,
258
+ ...(Object.keys(defs).length ? { $defs: defs } : {}),
259
+ },
260
+ effect: action.effect,
261
+ };
262
+ }
263
+
264
+ /**
265
+ * A store action carries no argument metadata of its own, so the prose comes from wherever the argument came
266
+ * from: an endpoint-named action borrows `<refName>.signal.<endpoint>.arg.<name>.desc`, and a field setter's one
267
+ * argument is the field, described at `<refName>.<field>.desc`. A slice role's `page`/`limit`/`sort` are the
268
+ * framework's own and have no dictionary entry to borrow.
269
+ */
270
+ #argSchema(action: SerializedStoreAction, arg: SerializedArg) {
271
+ const description = this.#argText(action, arg);
272
+ return {
273
+ ...this.#schema.arg(arg),
274
+ ...(description ? { description } : {}),
275
+ ...(arg.example !== undefined ? { examples: [arg.example] } : {}),
276
+ };
277
+ }
278
+
279
+ #argText({ refName, endpoint, field }: SerializedStoreAction, arg: SerializedArg) {
280
+ if (!refName) return undefined;
281
+ if (endpoint) return this.#text(`${refName}.signal.${endpoint}.arg.${arg.name}.desc`);
282
+ return field && arg.name === field ? this.#text(`${refName}.${field}.desc`) : undefined;
283
+ }
284
+
285
+ /**
286
+ * The words an agent picks the action by, borrowed from what the action is named after.
287
+ *
288
+ * The endpoint comes first, and it is right rather than merely adequate: the house rule makes `st.do.X` and
289
+ * `fetch.X` the same verb, so an action named after an endpoint reads as that endpoint's `.desc()`. A field
290
+ * setter falls back to the field's own label.
291
+ */
292
+ #texts({ refName, endpoint, field }: SerializedStoreAction) {
293
+ const keys = refName
294
+ ? [...(endpoint ? [`${refName}.signal.${endpoint}`] : []), ...(field ? [`${refName}.${field}`] : [])]
295
+ : [];
296
+ for (const key of keys) {
297
+ const title = this.#text(key);
298
+ const description = this.#text(`${key}.desc`);
299
+ if (title || description) return { ...(title ? { title } : {}), ...(description ? { description } : {}) };
300
+ }
301
+ return {};
302
+ }
303
+
304
+ #text(key: string) {
305
+ if (this.#options.resolveDescription) return this.#options.resolveDescription(key);
306
+ const locale = Translator.getActiveLocale() ?? parseAkanI18nEnv().defaultLocale;
307
+ const text = Translator.translateByLocale(locale, key);
308
+
309
+ return text === key ? undefined : text;
310
+ }
311
+
312
+ static #unwrap(value: unknown) {
313
+ return value instanceof DataList ? value.values : value;
314
+ }
315
+
316
+ /** True when nothing inside could be carrying a model's fields, so there is nothing a mask would have to strip. */
317
+ static #isPlainValue(value: unknown): boolean {
318
+ if (value === null || value === undefined) return true;
319
+ if (Array.isArray(value)) return value.every((item) => AgentBridge.#isPlainValue(item));
320
+ if (value instanceof Date) return true;
321
+ return typeof value !== "object";
322
+ }
323
+ }
@@ -0,0 +1,74 @@
1
+ import type { ContextBlock, SurfaceView } from "../../vendor/use-agentic";
2
+ import type { StoreInstance } from "../storeInstance";
3
+ import { StoreRegistry } from "../storeRegistry";
4
+ import type { AgentBridge } from "./AgentBridge";
5
+ import { ensureStoreSurface } from "./storeSurface";
6
+
7
+ /**
8
+ * The akan default context for a turn: where the user is (route), what is on screen (scopes and their curated
9
+ * resources), and which store keys the mounted components are reading (live keys). Live entries carry names and
10
+ * small primitives only — anything bigger is one `readState` call away, masked — so the block cannot bloat with
11
+ * what happens to be in the store. Base-store keys are excluded by the base store's own `agent: false`
12
+ * declaration, and the route block carries the three of them that matter.
13
+ */
14
+ export class AgentContext {
15
+ static of(): AgentContext {
16
+ return new AgentContext(StoreRegistry.instance, ensureStoreSurface().bridge);
17
+ }
18
+
19
+ readonly #instance: StoreInstance;
20
+ readonly #bridge: AgentBridge;
21
+
22
+ constructor(instance: StoreInstance, bridge: AgentBridge) {
23
+ this.#instance = instance;
24
+ this.#bridge = bridge;
25
+ }
26
+
27
+ blocks(surface: SurfaceView, view: string[] = []): ContextBlock[] {
28
+ const state = this.#instance.get();
29
+ const { scopes, resources } = surface.snapshot();
30
+ return [
31
+ {
32
+ kind: "route",
33
+ path: String(state.pathname ?? ""),
34
+ params: state.params ?? {},
35
+ searchParams: state.searchParams ?? {},
36
+ },
37
+ ...(scopes.length || resources.length ? [{ kind: "screen", scopes, resources }] : []),
38
+ ...this.#liveBlock(view.join(".")),
39
+ ];
40
+ }
41
+
42
+ #liveBlock(viewKey: string): ContextBlock[] {
43
+ const live = [...this.#instance.liveKeysIn(viewKey).keys()]
44
+ .filter((key) => this.#bridge.visibility.visibleKey(key))
45
+ .sort()
46
+ .map((key) => this.#liveEntry(key));
47
+ return live.length ? [{ kind: "state", live, note: "Call readState(key) to read a value." }] : [];
48
+ }
49
+
50
+ #liveEntry(key: string) {
51
+ const meta = this.#bridge.state[key];
52
+ const value = AgentContext.#inlineValue(this.#instance.get()[key]);
53
+ return {
54
+ key,
55
+ ...(meta?.refName ? { model: meta.refName } : {}),
56
+ ...(meta ? { type: meta.type } : {}),
57
+ ...(value !== undefined ? { value } : {}),
58
+ };
59
+ }
60
+
61
+ /** Primitives ride inline; anything bigger stays a name the agent pulls with `readState`. */
62
+ static #inlineValue(value: unknown): unknown {
63
+ if (value === null) return null;
64
+ switch (typeof value) {
65
+ case "string":
66
+ return value.length <= 200 ? value : `${value.slice(0, 200)}…`;
67
+ case "number":
68
+ case "boolean":
69
+ return value;
70
+ default:
71
+ return undefined;
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,82 @@
1
+ import { Translator } from "akanjs/client";
2
+ import { parseAkanI18nEnv } from "akanjs/common";
3
+ import { FetchClient } from "akanjs/fetch";
4
+ import type { PromptContent, PromptResult, SerializedSignal } from "akanjs/signal";
5
+ import type { ChatMessage } from "../../vendor/use-agentic";
6
+
7
+ export interface AgentPrompt {
8
+ name: string;
9
+ refName: string;
10
+ description?: string;
11
+ args: { name: string; required: boolean }[];
12
+ }
13
+
14
+ /**
15
+ * The user-invokable `prompt()` endpoints of the mounted app, read off the serialized signals the client already
16
+ * holds — the same catalogue MCP lists them from, so a chat needs no listing endpoint. Guards are not evaluated
17
+ * here: a prompt's own GET enforces them at call time, and the refusal lands in the transcript like any failure.
18
+ */
19
+ export class AgentPrompts {
20
+ static of(): AgentPrompts {
21
+ return new AgentPrompts(FetchClient.sharedSerializedSignal);
22
+ }
23
+
24
+ readonly #signals: Record<string, SerializedSignal>;
25
+
26
+ constructor(signals: Record<string, SerializedSignal>) {
27
+ this.#signals = signals;
28
+ }
29
+
30
+ list(): AgentPrompt[] {
31
+ const prompts: AgentPrompt[] = [];
32
+ for (const [refName, signal] of Object.entries(this.#signals))
33
+ for (const [name, endpoint] of Object.entries(signal.endpoint)) {
34
+ if (endpoint.type !== "prompt") continue;
35
+ prompts.push({
36
+ name,
37
+ refName,
38
+ ...(this.#description(refName, name) ? { description: this.#description(refName, name) } : {}),
39
+ args: (endpoint.args ?? []).map((arg) => ({
40
+ name: arg.name,
41
+ required: !(arg.nullable ?? arg.type === "search"),
42
+ })),
43
+ });
44
+ }
45
+ return prompts.sort((a, b) => (a.name < b.name ? -1 : 1));
46
+ }
47
+
48
+ find(name: string): AgentPrompt | null {
49
+ return this.list().find((prompt) => prompt.name === name) ?? null;
50
+ }
51
+
52
+ /** `/name arg1 arg2` — positional because a prompt's arguments are flat strings by protocol. */
53
+ static parseCommand(draft: string): { name: string; args: string[] } | null {
54
+ const match = /^\/([A-Za-z0-9_-]+)(?:\s+(.*))?$/.exec(draft.trim());
55
+ if (!match) return null;
56
+ return { name: match[1], args: match[2]?.split(/\s+/).filter(Boolean) ?? [] };
57
+ }
58
+
59
+ /** The messages a prompt returns become the user's turn, the way an MCP client sends a `prompts/get` result. */
60
+ static messagesOf(result: PromptResult): ChatMessage[] {
61
+ if (typeof result === "string") return [{ role: "user", text: result }];
62
+ return result.map((message) => ({ role: message.role, text: AgentPrompts.textOf(message.content) }));
63
+ }
64
+
65
+ static textOf(content: PromptContent): string {
66
+ if (content.type === "text") return content.text;
67
+ if (content.type === "resource") return `[resource ${content.resource.uri}]\n${content.resource.text}`;
68
+ if (content.type === "resource_link") return `[link ${content.name}: ${content.uri}]`;
69
+ return `[${content.type}]`;
70
+ }
71
+
72
+ #description(refName: string, name: string) {
73
+ return this.#text(`${refName}.signal.${name}.desc`) ?? this.#text(`${refName}.signal.${name}`);
74
+ }
75
+
76
+ #text(key: string) {
77
+ const locale = Translator.getActiveLocale() ?? parseAkanI18nEnv().defaultLocale;
78
+ const text = Translator.translateByLocale(locale, key);
79
+
80
+ return text === key ? undefined : text;
81
+ }
82
+ }