enya-agent 0.1.0

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 (389) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/ci.yml +70 -0
  3. package/.github/workflows/publish.yml +250 -0
  4. package/.gitmodules +3 -0
  5. package/Cargo.lock +3584 -0
  6. package/Cargo.toml +97 -0
  7. package/crates/enact/Cargo.toml +27 -0
  8. package/crates/enact/src/lib.rs +60 -0
  9. package/crates/enact-a2a/Cargo.toml +25 -0
  10. package/crates/enact-a2a/src/lib.rs +411 -0
  11. package/crates/enact-channels/Cargo.toml +64 -0
  12. package/crates/enact-channels/examples/README.md +80 -0
  13. package/crates/enact-channels/examples/channel_bot.rs +169 -0
  14. package/crates/enact-channels/examples/telegram-echo.rs +34 -0
  15. package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
  16. package/crates/enact-channels/src/config.rs +213 -0
  17. package/crates/enact-channels/src/lib.rs +25 -0
  18. package/crates/enact-channels/src/runtime.rs +237 -0
  19. package/crates/enact-channels/src/security/mod.rs +5 -0
  20. package/crates/enact-channels/src/security/pairing.rs +205 -0
  21. package/crates/enact-channels/src/teams.rs +601 -0
  22. package/crates/enact-channels/src/telegram.rs +2833 -0
  23. package/crates/enact-channels/src/traits.rs +200 -0
  24. package/crates/enact-channels/src/webhook.rs +262 -0
  25. package/crates/enact-channels/src/whatsapp.rs +310 -0
  26. package/crates/enact-cli/Cargo.toml +40 -0
  27. package/crates/enact-cli/src/commands/doctor.rs +62 -0
  28. package/crates/enact-cli/src/commands/mod.rs +3 -0
  29. package/crates/enact-cli/src/commands/run.rs +69 -0
  30. package/crates/enact-cli/src/commands/serve.rs +81 -0
  31. package/crates/enact-cli/src/config.rs +2 -0
  32. package/crates/enact-cli/src/main.rs +79 -0
  33. package/crates/enact-config/Cargo.toml +36 -0
  34. package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
  35. package/crates/enact-config/QUICK_REFERENCE.md +92 -0
  36. package/crates/enact-config/README.md +107 -0
  37. package/crates/enact-config/TESTING.md +161 -0
  38. package/crates/enact-config/examples/test-env-vars.rs +100 -0
  39. package/crates/enact-config/src/config.rs +399 -0
  40. package/crates/enact-config/src/encrypted_store.rs +211 -0
  41. package/crates/enact-config/src/lib.rs +298 -0
  42. package/crates/enact-config/src/secrets.rs +149 -0
  43. package/crates/enact-config/src/sync.rs +260 -0
  44. package/crates/enact-config/test-env-vars.sh +34 -0
  45. package/crates/enact-config/tests/README.md +99 -0
  46. package/crates/enact-config/tests/config_integration_test.rs +202 -0
  47. package/crates/enact-config/tests/security_test.rs +140 -0
  48. package/crates/enact-context/Cargo.toml +41 -0
  49. package/crates/enact-context/src/budget.rs +314 -0
  50. package/crates/enact-context/src/calibrator.rs +535 -0
  51. package/crates/enact-context/src/compactor.rs +392 -0
  52. package/crates/enact-context/src/condenser.rs +826 -0
  53. package/crates/enact-context/src/lib.rs +94 -0
  54. package/crates/enact-context/src/segment.rs +238 -0
  55. package/crates/enact-context/src/step_context.rs +645 -0
  56. package/crates/enact-context/src/token_counter.rs +148 -0
  57. package/crates/enact-context/src/window.rs +372 -0
  58. package/crates/enact-core/Cargo.toml +42 -0
  59. package/crates/enact-core/README.md +98 -0
  60. package/crates/enact-core/src/background/executor.rs +524 -0
  61. package/crates/enact-core/src/background/mod.rs +48 -0
  62. package/crates/enact-core/src/background/target_binding.rs +390 -0
  63. package/crates/enact-core/src/background/trigger.rs +511 -0
  64. package/crates/enact-core/src/callable/callable.rs +152 -0
  65. package/crates/enact-core/src/callable/composite.rs +817 -0
  66. package/crates/enact-core/src/callable/graph.rs +104 -0
  67. package/crates/enact-core/src/callable/llm.rs +211 -0
  68. package/crates/enact-core/src/callable/mod.rs +64 -0
  69. package/crates/enact-core/src/callable/registry.rs +206 -0
  70. package/crates/enact-core/src/context/execution_context.rs +757 -0
  71. package/crates/enact-core/src/context/invocation.rs +99 -0
  72. package/crates/enact-core/src/context/mod.rs +50 -0
  73. package/crates/enact-core/src/context/tenant.rs +175 -0
  74. package/crates/enact-core/src/context/trace.rs +127 -0
  75. package/crates/enact-core/src/flow/conditional.rs +293 -0
  76. package/crates/enact-core/src/flow/mod.rs +43 -0
  77. package/crates/enact-core/src/flow/parallel.rs +437 -0
  78. package/crates/enact-core/src/flow/repeat.rs +534 -0
  79. package/crates/enact-core/src/flow/sequential.rs +248 -0
  80. package/crates/enact-core/src/graph/checkpoint.rs +79 -0
  81. package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
  82. package/crates/enact-core/src/graph/compiled.rs +189 -0
  83. package/crates/enact-core/src/graph/edge.rs +59 -0
  84. package/crates/enact-core/src/graph/graph_schema.rs +218 -0
  85. package/crates/enact-core/src/graph/loader.rs +155 -0
  86. package/crates/enact-core/src/graph/mod.rs +18 -0
  87. package/crates/enact-core/src/graph/node/function.rs +49 -0
  88. package/crates/enact-core/src/graph/node/mod.rs +48 -0
  89. package/crates/enact-core/src/graph/schema.rs +62 -0
  90. package/crates/enact-core/src/inbox/message.rs +405 -0
  91. package/crates/enact-core/src/inbox/mod.rs +31 -0
  92. package/crates/enact-core/src/inbox/store.rs +355 -0
  93. package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
  94. package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
  95. package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
  96. package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
  97. package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
  98. package/crates/enact-core/src/kernel/error.rs +1200 -0
  99. package/crates/enact-core/src/kernel/event.rs +1394 -0
  100. package/crates/enact-core/src/kernel/execution_model.rs +831 -0
  101. package/crates/enact-core/src/kernel/execution_state.rs +189 -0
  102. package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
  103. package/crates/enact-core/src/kernel/ids.rs +2086 -0
  104. package/crates/enact-core/src/kernel/interrupt.rs +125 -0
  105. package/crates/enact-core/src/kernel/kernel.rs +1283 -0
  106. package/crates/enact-core/src/kernel/mod.rs +205 -0
  107. package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
  108. package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
  109. package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
  110. package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
  111. package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
  112. package/crates/enact-core/src/kernel/reducer.rs +808 -0
  113. package/crates/enact-core/src/kernel/replay.rs +153 -0
  114. package/crates/enact-core/src/lib.rs +413 -0
  115. package/crates/enact-core/src/memory/episodic.rs +0 -0
  116. package/crates/enact-core/src/memory/mod.rs +6 -0
  117. package/crates/enact-core/src/memory/semantic.rs +0 -0
  118. package/crates/enact-core/src/memory/trait.rs +0 -0
  119. package/crates/enact-core/src/memory/vector_db.rs +0 -0
  120. package/crates/enact-core/src/memory/working.rs +0 -0
  121. package/crates/enact-core/src/policy/execution_policy.rs +292 -0
  122. package/crates/enact-core/src/policy/filters.rs +458 -0
  123. package/crates/enact-core/src/policy/input_processor.rs +407 -0
  124. package/crates/enact-core/src/policy/long_running.rs +134 -0
  125. package/crates/enact-core/src/policy/mod.rs +193 -0
  126. package/crates/enact-core/src/policy/pii_input.rs +274 -0
  127. package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
  128. package/crates/enact-core/src/policy/tool_policy.rs +407 -0
  129. package/crates/enact-core/src/providers/mod.rs +63 -0
  130. package/crates/enact-core/src/providers/trait.rs +292 -0
  131. package/crates/enact-core/src/runner/callbacks.rs +6 -0
  132. package/crates/enact-core/src/runner/execution_runner.rs +476 -0
  133. package/crates/enact-core/src/runner/loop.rs +117 -0
  134. package/crates/enact-core/src/runner/mod.rs +58 -0
  135. package/crates/enact-core/src/runner/protected_runner.rs +280 -0
  136. package/crates/enact-core/src/signal/inmemory.rs +231 -0
  137. package/crates/enact-core/src/signal/mod.rs +108 -0
  138. package/crates/enact-core/src/streaming/event_logger.rs +195 -0
  139. package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
  140. package/crates/enact-core/src/streaming/mod.rs +108 -0
  141. package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
  142. package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
  143. package/crates/enact-core/src/streaming/protection/context.rs +136 -0
  144. package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
  145. package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
  146. package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
  147. package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
  148. package/crates/enact-core/src/streaming/sse.rs +0 -0
  149. package/crates/enact-core/src/telemetry/exporter.rs +0 -0
  150. package/crates/enact-core/src/telemetry/init.rs +0 -0
  151. package/crates/enact-core/src/telemetry/mod.rs +49 -0
  152. package/crates/enact-core/src/telemetry/spans.rs +245 -0
  153. package/crates/enact-core/src/tool/agent_tool.rs +177 -0
  154. package/crates/enact-core/src/tool/browser/mod.rs +0 -0
  155. package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
  156. package/crates/enact-core/src/tool/cost.rs +247 -0
  157. package/crates/enact-core/src/tool/discovery.rs +0 -0
  158. package/crates/enact-core/src/tool/dispatcher.rs +347 -0
  159. package/crates/enact-core/src/tool/filesystem.rs +231 -0
  160. package/crates/enact-core/src/tool/function.rs +99 -0
  161. package/crates/enact-core/src/tool/git.rs +162 -0
  162. package/crates/enact-core/src/tool/http.rs +214 -0
  163. package/crates/enact-core/src/tool/mcp/client.rs +0 -0
  164. package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
  165. package/crates/enact-core/src/tool/mod.rs +51 -0
  166. package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
  167. package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
  168. package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
  169. package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
  170. package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
  171. package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
  172. package/crates/enact-core/src/tool/shell.rs +147 -0
  173. package/crates/enact-core/src/tool/trait.rs +33 -0
  174. package/crates/enact-core/src/tool/web_search.rs +277 -0
  175. package/crates/enact-core/src/util/config.rs +0 -0
  176. package/crates/enact-core/src/util/errors.rs +0 -0
  177. package/crates/enact-core/src/util/mod.rs +6 -0
  178. package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
  179. package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
  180. package/crates/enact-core/tests/e2e_test.rs +259 -0
  181. package/crates/enact-core/tests/graph_test.rs +130 -0
  182. package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
  183. package/crates/enact-cron/Cargo.toml +28 -0
  184. package/crates/enact-cron/src/lib.rs +44 -0
  185. package/crates/enact-cron/src/schedule.rs +156 -0
  186. package/crates/enact-cron/src/store.rs +589 -0
  187. package/crates/enact-cron/src/types.rs +148 -0
  188. package/crates/enact-gateway/Cargo.toml +31 -0
  189. package/crates/enact-gateway/README.md +30 -0
  190. package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
  191. package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
  192. package/crates/enact-gateway/src/lib.rs +582 -0
  193. package/crates/enact-mcp/Cargo.toml +24 -0
  194. package/crates/enact-mcp/src/lib.rs +178 -0
  195. package/crates/enact-memory/Cargo.toml +25 -0
  196. package/crates/enact-memory/src/backend.rs +20 -0
  197. package/crates/enact-memory/src/chunker.rs +230 -0
  198. package/crates/enact-memory/src/embeddings.rs +221 -0
  199. package/crates/enact-memory/src/lib.rs +67 -0
  200. package/crates/enact-memory/src/markdown.rs +127 -0
  201. package/crates/enact-memory/src/none.rs +61 -0
  202. package/crates/enact-memory/src/sqlite.rs +276 -0
  203. package/crates/enact-memory/src/traits.rs +65 -0
  204. package/crates/enact-memory/src/vector.rs +198 -0
  205. package/crates/enact-oauth/Cargo.toml +27 -0
  206. package/crates/enact-oauth/src/lib.rs +584 -0
  207. package/crates/enact-observability/Cargo.toml +22 -0
  208. package/crates/enact-observability/src/lib.rs +197 -0
  209. package/crates/enact-providers/Cargo.toml +33 -0
  210. package/crates/enact-providers/examples/hello-agent.rs +33 -0
  211. package/crates/enact-providers/src/anthropic.rs +182 -0
  212. package/crates/enact-providers/src/azure.rs +96 -0
  213. package/crates/enact-providers/src/bridge.rs +221 -0
  214. package/crates/enact-providers/src/gemini.rs +227 -0
  215. package/crates/enact-providers/src/http.rs +78 -0
  216. package/crates/enact-providers/src/lib.rs +53 -0
  217. package/crates/enact-providers/src/openai_compatible.rs +167 -0
  218. package/crates/enact-providers/src/openrouter.rs +33 -0
  219. package/crates/enact-runner/Cargo.toml +24 -0
  220. package/crates/enact-runner/README.md +76 -0
  221. package/crates/enact-runner/src/compaction.rs +225 -0
  222. package/crates/enact-runner/src/config.rs +118 -0
  223. package/crates/enact-runner/src/lib.rs +63 -0
  224. package/crates/enact-runner/src/loop_driver.rs +414 -0
  225. package/crates/enact-runner/src/parser.rs +421 -0
  226. package/crates/enact-runner/src/retry.rs +262 -0
  227. package/crates/enact-runner/tests/integration.rs +278 -0
  228. package/crates/enact-security/Cargo.toml +22 -0
  229. package/crates/enact-security/src/audit.rs +375 -0
  230. package/crates/enact-security/src/lib.rs +37 -0
  231. package/crates/enact-security/src/policy.rs +406 -0
  232. package/crates/enact-skills/Cargo.toml +25 -0
  233. package/crates/enact-skills/src/lib.rs +506 -0
  234. package/crates/enact-tools/Cargo.toml +22 -0
  235. package/crates/enact-tools/src/file_read.rs +166 -0
  236. package/crates/enact-tools/src/file_write.rs +216 -0
  237. package/crates/enact-tools/src/git_operations.rs +513 -0
  238. package/crates/enact-tools/src/http_request.rs +417 -0
  239. package/crates/enact-tools/src/lib.rs +104 -0
  240. package/crates/enact-tools/src/security.rs +227 -0
  241. package/crates/enact-tools/src/shell.rs +191 -0
  242. package/crates/enact-tools/src/traits.rs +159 -0
  243. package/docs/Makefile +74 -0
  244. package/docs/config.toml +62 -0
  245. package/docs/content/_index.md +174 -0
  246. package/docs/content/a2a/_index.md +431 -0
  247. package/docs/content/api/_index.md +323 -0
  248. package/docs/content/channels/_index.md +160 -0
  249. package/docs/content/channels/teams.md +205 -0
  250. package/docs/content/channels/telegram.md +182 -0
  251. package/docs/content/channels/webhook.md +423 -0
  252. package/docs/content/channels/whatsapp.md +240 -0
  253. package/docs/content/cli/_index.md +261 -0
  254. package/docs/content/concepts/_index.md +273 -0
  255. package/docs/content/configuration/_index.md +241 -0
  256. package/docs/content/cron/_index.md +248 -0
  257. package/docs/content/developers/_index.md +278 -0
  258. package/docs/content/getting-started/_index.md +180 -0
  259. package/docs/content/installation/_index.md +186 -0
  260. package/docs/content/installation/uninstall.md +101 -0
  261. package/docs/content/installation/updating.md +120 -0
  262. package/docs/content/mcp/_index.md +215 -0
  263. package/docs/content/memory/_index.md +163 -0
  264. package/docs/content/oauth/_index.md +515 -0
  265. package/docs/content/providers/_index.md +206 -0
  266. package/docs/content/roadmap/_index.md +199 -0
  267. package/docs/content/security/_index.md +219 -0
  268. package/docs/content/skills/_index.md +228 -0
  269. package/docs/content/tools/_index.md +485 -0
  270. package/docs/content/troubleshooting/_index.md +259 -0
  271. package/docs/content/yaml-schema/_index.md +294 -0
  272. package/docs/static/giallo-dark.css +91 -0
  273. package/docs/static/giallo-light.css +91 -0
  274. package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
  275. package/docs/themes/tanuki/LICENSE +21 -0
  276. package/docs/themes/tanuki/README.md +166 -0
  277. package/docs/themes/tanuki/examples/blog/config.toml +58 -0
  278. package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
  279. package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
  280. package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
  281. package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
  282. package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
  283. package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
  284. package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
  285. package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
  286. package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
  287. package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
  288. package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
  289. package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
  290. package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
  291. package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
  292. package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
  293. package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
  294. package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
  295. package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
  296. package/docs/themes/tanuki/examples/book/config.toml +17 -0
  297. package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
  298. package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
  299. package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
  300. package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
  301. package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
  302. package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
  303. package/docs/themes/tanuki/examples/book/content/print.md +6 -0
  304. package/docs/themes/tanuki/examples/docs/config.toml +28 -0
  305. package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
  306. package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
  307. package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
  308. package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
  309. package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
  310. package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
  311. package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
  312. package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
  313. package/docs/themes/tanuki/examples/index.html +2104 -0
  314. package/docs/themes/tanuki/mise.toml +108 -0
  315. package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
  316. package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
  317. package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
  318. package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
  319. package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
  320. package/docs/themes/tanuki/sass/components/_code.scss +457 -0
  321. package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
  322. package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
  323. package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
  324. package/docs/themes/tanuki/sass/components/_print.scss +237 -0
  325. package/docs/themes/tanuki/sass/components/_search.scss +224 -0
  326. package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
  327. package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
  328. package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
  329. package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
  330. package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
  331. package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
  332. package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
  333. package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
  334. package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
  335. package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
  336. package/docs/themes/tanuki/sass/style.scss +47 -0
  337. package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
  338. package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
  339. package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
  340. package/docs/themes/tanuki/screenshot.png +0 -0
  341. package/docs/themes/tanuki/sentinel.kdl +59 -0
  342. package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
  343. package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
  344. package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
  345. package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
  346. package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
  347. package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
  348. package/docs/themes/tanuki/static/js/anchors.js +18 -0
  349. package/docs/themes/tanuki/static/js/app.js +274 -0
  350. package/docs/themes/tanuki/static/js/code.js +394 -0
  351. package/docs/themes/tanuki/static/js/navigation.js +778 -0
  352. package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
  353. package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
  354. package/docs/themes/tanuki/static/js/search.js +215 -0
  355. package/docs/themes/tanuki/static/js/theme.js +169 -0
  356. package/docs/themes/tanuki/static/syntax-dark.css +151 -0
  357. package/docs/themes/tanuki/static/syntax-light.css +151 -0
  358. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
  359. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
  360. package/docs/themes/tanuki/templates/404.html +52 -0
  361. package/docs/themes/tanuki/templates/base.html +428 -0
  362. package/docs/themes/tanuki/templates/blog.html +66 -0
  363. package/docs/themes/tanuki/templates/home.html +108 -0
  364. package/docs/themes/tanuki/templates/index.html +178 -0
  365. package/docs/themes/tanuki/templates/landing.html +168 -0
  366. package/docs/themes/tanuki/templates/macros/nav.html +128 -0
  367. package/docs/themes/tanuki/templates/macros/posts.html +101 -0
  368. package/docs/themes/tanuki/templates/macros/ui.html +159 -0
  369. package/docs/themes/tanuki/templates/page.html +135 -0
  370. package/docs/themes/tanuki/templates/partials/footer.html +38 -0
  371. package/docs/themes/tanuki/templates/partials/header.html +366 -0
  372. package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
  373. package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
  374. package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
  375. package/docs/themes/tanuki/templates/partials/search.html +52 -0
  376. package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
  377. package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
  378. package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
  379. package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
  380. package/docs/themes/tanuki/templates/print.html +244 -0
  381. package/docs/themes/tanuki/templates/section.html +186 -0
  382. package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
  383. package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
  384. package/docs/themes/tanuki/theme.toml +58 -0
  385. package/examples/hello-agent.rs +55 -0
  386. package/package.json +36 -0
  387. package/proto/config.proto +60 -0
  388. package/proto/events.proto +0 -0
  389. package/proto/runtime.proto +215 -0
@@ -0,0 +1,277 @@
1
+ //! Web search tool
2
+
3
+ use crate::tool::Tool;
4
+ use async_trait::async_trait;
5
+ use serde_json::json;
6
+ use std::time::Duration;
7
+
8
+ const DEFAULT_MAX_RESULTS: usize = 5;
9
+ const DEFAULT_TIMEOUT_SECS: u64 = 30;
10
+
11
+ /// Web search tool supporting DuckDuckGo (free) and Brave (API key required)
12
+ pub struct WebSearchTool {
13
+ provider: String,
14
+ brave_api_key: Option<String>,
15
+ max_results: usize,
16
+ timeout_secs: u64,
17
+ }
18
+
19
+ impl WebSearchTool {
20
+ pub fn new(provider: impl Into<String>) -> Self {
21
+ Self {
22
+ provider: provider.into().to_lowercase(),
23
+ brave_api_key: None,
24
+ max_results: DEFAULT_MAX_RESULTS,
25
+ timeout_secs: DEFAULT_TIMEOUT_SECS,
26
+ }
27
+ }
28
+
29
+ pub fn with_brave_key(mut self, key: impl Into<String>) -> Self {
30
+ self.brave_api_key = Some(key.into());
31
+ self
32
+ }
33
+
34
+ pub fn with_max_results(mut self, max: usize) -> Self {
35
+ self.max_results = max.clamp(1, 10);
36
+ self
37
+ }
38
+
39
+ pub fn with_timeout(mut self, secs: u64) -> Self {
40
+ self.timeout_secs = secs.max(1);
41
+ self
42
+ }
43
+
44
+ async fn search_duckduckgo(&self,
45
+ query: &str,
46
+ ) -> anyhow::Result<Vec<SearchResult>> {
47
+ let encoded_query = urlencoding::encode(query);
48
+ let search_url = format!(
49
+ "https://html.duckduckgo.com/html/?q={}",
50
+ encoded_query
51
+ );
52
+
53
+ let client = reqwest::Client::builder()
54
+ .timeout(Duration::from_secs(self.timeout_secs))
55
+ .user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36")
56
+ .build()?;
57
+
58
+ let response = client.get(&search_url).send().await?;
59
+
60
+ if !response.status().is_success() {
61
+ anyhow::bail!(
62
+ "DuckDuckGo search failed: {}",
63
+ response.status()
64
+ );
65
+ }
66
+
67
+ let html = response.text().await?;
68
+ self.parse_duckduckgo_results(&html)
69
+ }
70
+
71
+ fn parse_duckduckgo_results(&self,
72
+ html: &str,
73
+ ) -> anyhow::Result<Vec<SearchResult>> {
74
+ let mut results = Vec::new();
75
+
76
+ // Simple regex-based parsing (in production, use a proper HTML parser)
77
+ let result_regex = regex::Regex::new(
78
+ r#"class="result__a"[^>]*href="([^"]+)"[^>]*>([^<]+)"#,
79
+ )?;
80
+
81
+ for cap in result_regex.captures_iter(html) {
82
+ if results.len() >= self.max_results {
83
+ break;
84
+ }
85
+
86
+ let url = cap.get(1).map(|m| m.as_str()).unwrap_or("");
87
+ let title = cap.get(2).map(|m| m.as_str()).unwrap_or("");
88
+
89
+ // Clean up the URL (DuckDuckGo uses redirects)
90
+ let url = if url.starts_with("//duckduckgo.com/l/?") {
91
+ // Extract actual URL from redirect
92
+ url.split("uddg=")
93
+ .nth(1)
94
+ .and_then(|u| urlencoding::decode(u).ok())
95
+ .map(|s| s.to_string())
96
+ .unwrap_or_else(|| url.to_string())
97
+ } else {
98
+ url.to_string()
99
+ };
100
+
101
+ let mut decoded_title = String::new();
102
+ html_escape::decode_html_entities_to_string(title, &mut decoded_title);
103
+ results.push(SearchResult {
104
+ title: decoded_title,
105
+ url,
106
+ snippet: String::new(),
107
+ });
108
+ }
109
+
110
+ Ok(results)
111
+ }
112
+
113
+ async fn search_brave(&self, query: &str) -> anyhow::Result<Vec<SearchResult>> {
114
+ let api_key = self
115
+ .brave_api_key
116
+ .as_ref()
117
+ .ok_or_else(|| anyhow::anyhow!("Brave API key required"))?;
118
+
119
+ let client = reqwest::Client::builder()
120
+ .timeout(Duration::from_secs(self.timeout_secs))
121
+ .build()?;
122
+
123
+ let response = client
124
+ .get("https://api.search.brave.com/res/v1/web/search")
125
+ .header("Accept", "application/json")
126
+ .header("X-Subscription-Token", api_key)
127
+ .query(&[("q", query), ("count", &self.max_results.to_string())])
128
+ .send()
129
+ .await?;
130
+
131
+ if !response.status().is_success() {
132
+ let status = response.status();
133
+ let body = response.text().await.unwrap_or_default();
134
+ anyhow::bail!("Brave search failed ({}): {}", status, body);
135
+ }
136
+
137
+ let data: serde_json::Value = response.json().await?;
138
+ let mut results = Vec::new();
139
+
140
+ if let Some(web) = data.get("web") {
141
+ if let Some(pages) = web.get("results").and_then(|r| r.as_array()) {
142
+ for page in pages.iter().take(self.max_results) {
143
+ results.push(SearchResult {
144
+ title: page
145
+ .get("title")
146
+ .and_then(|t| t.as_str())
147
+ .unwrap_or("")
148
+ .to_string(),
149
+ url: page
150
+ .get("url")
151
+ .and_then(|u| u.as_str())
152
+ .unwrap_or("")
153
+ .to_string(),
154
+ snippet: page
155
+ .get("description")
156
+ .and_then(|d| d.as_str())
157
+ .unwrap_or("")
158
+ .to_string(),
159
+ });
160
+ }
161
+ }
162
+ }
163
+
164
+ Ok(results)
165
+ }
166
+ }
167
+
168
+ #[derive(Debug, Clone)]
169
+ struct SearchResult {
170
+ title: String,
171
+ url: String,
172
+ snippet: String,
173
+ }
174
+
175
+ #[async_trait]
176
+ impl Tool for WebSearchTool {
177
+ fn name(&self) -> &str {
178
+ "web_search"
179
+ }
180
+
181
+ fn description(&self) -> &str {
182
+ "Search the web using DuckDuckGo (free) or Brave (requires API key)"
183
+ }
184
+
185
+ fn parameters_schema(&self) -> serde_json::Value {
186
+ json!({
187
+ "type": "object",
188
+ "properties": {
189
+ "query": {
190
+ "type": "string",
191
+ "description": "Search query"
192
+ },
193
+ "max_results": {
194
+ "type": "integer",
195
+ "description": "Maximum number of results (1-10)",
196
+ "minimum": 1,
197
+ "maximum": 10,
198
+ "default": 5
199
+ }
200
+ },
201
+ "required": ["query"]
202
+ })
203
+ }
204
+
205
+ fn requires_network(&self) -> bool {
206
+ true
207
+ }
208
+
209
+ async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
210
+ let query = args
211
+ .get("query")
212
+ .and_then(|v| v.as_str())
213
+ .ok_or_else(|| anyhow::anyhow!("Missing 'query' parameter"))?;
214
+
215
+ let max_results = args
216
+ .get("max_results")
217
+ .and_then(|v| v.as_u64())
218
+ .map(|n| n as usize)
219
+ .unwrap_or(DEFAULT_MAX_RESULTS)
220
+ .clamp(1, 10);
221
+
222
+ let results = match self.provider.as_str() {
223
+ "brave" => self.search_brave(query).await?,
224
+ "duckduckgo" | _ => self.search_duckduckgo(query).await?,
225
+ };
226
+
227
+ let results_json: Vec<serde_json::Value> = results
228
+ .into_iter()
229
+ .take(max_results)
230
+ .map(|r| {
231
+ json!({
232
+ "title": r.title,
233
+ "url": r.url,
234
+ "snippet": r.snippet
235
+ })
236
+ })
237
+ .collect();
238
+
239
+ Ok(json!({
240
+ "success": true,
241
+ "query": query,
242
+ "provider": self.provider,
243
+ "results": results_json,
244
+ "count": results_json.len()
245
+ }))
246
+ }
247
+ }
248
+
249
+ #[cfg(test)]
250
+ mod tests {
251
+ use super::*;
252
+
253
+ #[tokio::test]
254
+ async fn test_web_search_duckduckgo() {
255
+ let tool = WebSearchTool::new("duckduckgo");
256
+ let result = tool
257
+ .execute(json!({
258
+ "query": "Rust programming language",
259
+ "max_results": 3
260
+ }))
261
+ .await;
262
+
263
+ // May fail if no network, but should parse correctly
264
+ if let Ok(response) = result {
265
+ assert_eq!(response["success"], true);
266
+ assert!(response["count"].as_u64().unwrap_or(0) > 0);
267
+ }
268
+ }
269
+
270
+ #[test]
271
+ fn test_web_search_schema() {
272
+ let tool = WebSearchTool::new("duckduckgo");
273
+ let schema = tool.parameters_schema();
274
+ assert!(schema["properties"]["query"].is_object());
275
+ assert!(schema["properties"]["max_results"].is_object());
276
+ }
277
+ }
File without changes
File without changes
@@ -0,0 +1,6 @@
1
+ //! Shared utility types and helpers.
2
+ //!
3
+ //! This module will host common utilities such as
4
+ //! error types, and configuration structures.
5
+
6
+
@@ -0,0 +1,291 @@
1
+ //! Air-Gapped E2E Tests - Verify zero network egress
2
+ //!
3
+ //! These tests verify that air-gapped mode:
4
+ //! 1. Blocks network-requiring providers
5
+ //! 2. Blocks network-requiring tools
6
+ //! 3. Produces NetworkViolation errors
7
+ //! 4. Allows local-only operations to proceed
8
+ //!
9
+ //! @see docs/feat-07-air-gapped-hardening.md
10
+
11
+ use enact_core::kernel::{
12
+ ExecutionKernel, ExecutionState, StepType, ViolationType,
13
+ ExecutionErrorCategory, TenantId,
14
+ };
15
+ use enact_core::context::TenantContext;
16
+ use enact_core::providers::{ModelProvider, ChatRequest, ChatResponse, ChatMessage, ModelCapabilities};
17
+ use enact_core::tool::Tool;
18
+ use async_trait::async_trait;
19
+ use serde_json::Value;
20
+ use std::sync::Arc;
21
+
22
+ /// Helper to create a test TenantContext
23
+ fn test_tenant() -> TenantContext {
24
+ TenantContext::new(TenantId::from("tenant_test"))
25
+ }
26
+
27
+ // =============================================================================
28
+ // Mock Network-Requiring Provider
29
+ // =============================================================================
30
+
31
+ struct MockNetworkProvider;
32
+
33
+ #[async_trait]
34
+ impl ModelProvider for MockNetworkProvider {
35
+ fn name(&self) -> &str {
36
+ "mock-network-provider"
37
+ }
38
+
39
+ fn requires_network(&self) -> bool {
40
+ true // This provider requires network
41
+ }
42
+
43
+ fn capabilities(&self) -> ModelCapabilities {
44
+ ModelCapabilities::default()
45
+ }
46
+
47
+ async fn chat(&self, _request: ChatRequest) -> anyhow::Result<ChatResponse> {
48
+ // This would normally make a network call
49
+ // In air-gapped mode, this should be blocked before reaching here
50
+ anyhow::bail!("Network call attempted (should be blocked in air-gapped mode)")
51
+ }
52
+ }
53
+
54
+ // =============================================================================
55
+ // Mock Local Provider
56
+ // =============================================================================
57
+
58
+ struct MockLocalProvider;
59
+
60
+ #[async_trait]
61
+ impl ModelProvider for MockLocalProvider {
62
+ fn name(&self) -> &str {
63
+ "mock-local-provider"
64
+ }
65
+
66
+ fn requires_network(&self) -> bool {
67
+ false // This provider does not require network
68
+ }
69
+
70
+ fn capabilities(&self) -> ModelCapabilities {
71
+ ModelCapabilities::default()
72
+ }
73
+
74
+ async fn chat(&self, _request: ChatRequest) -> anyhow::Result<ChatResponse> {
75
+ // Local provider - no network needed
76
+ Ok(ChatResponse {
77
+ id: "local-response-id".to_string(),
78
+ choices: vec![enact_core::providers::ChatChoice {
79
+ index: 0,
80
+ message: ChatMessage::assistant("Local response"),
81
+ finish_reason: Some("stop".to_string()),
82
+ }],
83
+ usage: Some(enact_core::providers::ChatUsage {
84
+ prompt_tokens: 10,
85
+ completion_tokens: 5,
86
+ total_tokens: 15,
87
+ }),
88
+ })
89
+ }
90
+ }
91
+
92
+ // =============================================================================
93
+ // Mock Network-Requiring Tool
94
+ // =============================================================================
95
+
96
+ struct MockNetworkTool;
97
+
98
+ #[async_trait]
99
+ impl Tool for MockNetworkTool {
100
+ fn name(&self) -> &str {
101
+ "mock_network_tool"
102
+ }
103
+
104
+ fn description(&self) -> &str {
105
+ "A tool that requires network access"
106
+ }
107
+
108
+ fn requires_network(&self) -> bool {
109
+ true // This tool requires network
110
+ }
111
+
112
+ async fn execute(&self, _args: Value) -> anyhow::Result<Value> {
113
+ // This would normally make a network call
114
+ anyhow::bail!("Network call attempted (should be blocked in air-gapped mode)")
115
+ }
116
+ }
117
+
118
+ // =============================================================================
119
+ // Mock Local Tool
120
+ // =============================================================================
121
+
122
+ struct MockLocalTool;
123
+
124
+ #[async_trait]
125
+ impl Tool for MockLocalTool {
126
+ fn name(&self) -> &str {
127
+ "mock_local_tool"
128
+ }
129
+
130
+ fn description(&self) -> &str {
131
+ "A tool that does not require network access"
132
+ }
133
+
134
+ fn requires_network(&self) -> bool {
135
+ false // This tool does not require network
136
+ }
137
+
138
+ async fn execute(&self, _args: Value) -> anyhow::Result<Value> {
139
+ // Local tool - no network needed
140
+ Ok(serde_json::json!({"result": "local computation"}))
141
+ }
142
+ }
143
+
144
+ // =============================================================================
145
+ // Test Cases
146
+ // =============================================================================
147
+
148
+ /// Test that network-requiring providers are detected and blocked
149
+ ///
150
+ /// This test verifies that:
151
+ /// - Providers with `requires_network() == true` are identified
152
+ /// - Attempting to use them in air-gapped mode should raise NetworkViolation
153
+ #[tokio::test]
154
+ async fn test_airgapped_blocks_network_provider() {
155
+ let network_provider = Arc::new(MockNetworkProvider);
156
+
157
+ // Verify the provider declares it requires network
158
+ assert!(network_provider.requires_network(), "Provider should declare network requirement");
159
+
160
+ // In a real implementation, the enforcement middleware would check this
161
+ // and raise a NetworkViolation before the provider is called.
162
+ // For now, we verify the provider correctly declares its requirement.
163
+ }
164
+
165
+ /// Test that network-requiring tools are detected and blocked
166
+ ///
167
+ /// This test verifies that:
168
+ /// - Tools with `requires_network() == true` are identified
169
+ /// - Attempting to use them in air-gapped mode should raise NetworkViolation
170
+ #[tokio::test]
171
+ async fn test_airgapped_blocks_network_tool() {
172
+ let network_tool = Arc::new(MockNetworkTool);
173
+
174
+ // Verify the tool declares it requires network
175
+ assert!(network_tool.requires_network(), "Tool should declare network requirement");
176
+
177
+ // In a real implementation, the enforcement middleware would check this
178
+ // and raise a NetworkViolation before the tool is called.
179
+ // For now, we verify the tool correctly declares its requirement.
180
+ }
181
+
182
+ /// Test that local providers are allowed in air-gapped mode
183
+ ///
184
+ /// This test verifies that:
185
+ /// - Providers with `requires_network() == false` are allowed
186
+ /// - They can be used without raising NetworkViolation
187
+ #[tokio::test]
188
+ async fn test_airgapped_allows_local_provider() {
189
+ let local_provider = Arc::new(MockLocalProvider);
190
+
191
+ // Verify the provider declares it does not require network
192
+ assert!(!local_provider.requires_network(), "Provider should declare no network requirement");
193
+
194
+ // The provider should be usable in air-gapped mode
195
+ let request = ChatRequest {
196
+ messages: vec![ChatMessage::user("test")],
197
+ temperature: None,
198
+ max_tokens: None,
199
+ };
200
+
201
+ let response = local_provider.chat(request).await;
202
+ assert!(response.is_ok(), "Local provider should work in air-gapped mode");
203
+ }
204
+
205
+ /// Test that local tools are allowed in air-gapped mode
206
+ ///
207
+ /// This test verifies that:
208
+ /// - Tools with `requires_network() == false` are allowed
209
+ /// - They can be used without raising NetworkViolation
210
+ #[tokio::test]
211
+ async fn test_airgapped_allows_local_tool() {
212
+ let local_tool = Arc::new(MockLocalTool);
213
+
214
+ // Verify the tool declares it does not require network
215
+ assert!(!local_tool.requires_network(), "Tool should declare no network requirement");
216
+
217
+ // The tool should be usable in air-gapped mode
218
+ let result = local_tool.execute(serde_json::json!({})).await;
219
+ assert!(result.is_ok(), "Local tool should work in air-gapped mode");
220
+ }
221
+
222
+ /// Test that NetworkViolation error type is correctly formatted
223
+ ///
224
+ /// This test verifies that:
225
+ /// - NetworkViolation maps to PolicyViolation category
226
+ /// - Error is non-retryable
227
+ /// - Error code is "network_violation"
228
+ #[tokio::test]
229
+ async fn test_network_violation_error_format() {
230
+ use enact_core::kernel::EnforcementViolation;
231
+
232
+ let violation = EnforcementViolation::new(ViolationType::NetworkViolation, 0, 0);
233
+ let error = violation.to_error();
234
+
235
+ // Verify error category
236
+ assert_eq!(error.category, ExecutionErrorCategory::PolicyViolation);
237
+
238
+ // Verify error code
239
+ assert_eq!(error.code, Some("network_violation".to_string()));
240
+
241
+ // Verify error is non-retryable (PolicyViolation is fatal)
242
+ assert!(!error.retry_policy.retryable);
243
+ assert!(error.category.is_fatal());
244
+ }
245
+
246
+ /// Test that execution kernel can be created in air-gapped context
247
+ ///
248
+ /// This test verifies that:
249
+ /// - ExecutionKernel can be instantiated
250
+ /// - Basic operations work without network
251
+ #[tokio::test]
252
+ async fn test_airgapped_kernel_basic_operations() {
253
+ let mut kernel = ExecutionKernel::new(test_tenant());
254
+
255
+ // Start execution
256
+ kernel.start().unwrap();
257
+ assert_eq!(kernel.state(), ExecutionState::Running);
258
+
259
+ // Begin a step (local operation, no network needed)
260
+ let step_id = kernel.begin_step(
261
+ StepType::LlmNode,
262
+ "test_step",
263
+ None,
264
+ ).unwrap();
265
+
266
+ // Complete the step
267
+ kernel.complete_step(step_id, Some("output".to_string()), 100).unwrap();
268
+
269
+ // Complete execution
270
+ kernel.complete(Some("final output".to_string())).unwrap();
271
+ assert_eq!(kernel.state(), ExecutionState::Completed);
272
+ }
273
+
274
+ /// Test that NetworkViolation is properly serialized
275
+ ///
276
+ /// This test verifies that:
277
+ /// - ViolationType::NetworkViolation serializes correctly
278
+ /// - Error messages are formatted correctly
279
+ #[tokio::test]
280
+ async fn test_network_violation_serialization() {
281
+ use enact_core::kernel::EnforcementViolation;
282
+
283
+ let violation = EnforcementViolation::new(ViolationType::NetworkViolation, 0, 0);
284
+
285
+ // Verify string representation
286
+ assert_eq!(format!("{}", ViolationType::NetworkViolation), "network_violation");
287
+
288
+ // Verify violation message format
289
+ assert!(violation.message.contains("network_violation"));
290
+ }
291
+
@@ -0,0 +1,119 @@
1
+ use enact_core::kernel::{ExecutionEventType, ExecutionId, StepId};
2
+ use enact_core::streaming::{EventEmitter, EventLog, StreamEvent};
3
+ use enact_core::{EventStore, InMemoryEventStore};
4
+ use std::sync::Arc;
5
+ use tokio::time::Duration;
6
+
7
+ #[tokio::test]
8
+ async fn test_agentic_loop_event_persistence() {
9
+ // 1. Setup - Create an EventStore and EventEmitter
10
+ // InMemoryEventStore implements enact_core::EventStore (which is the streaming one)
11
+ let event_store = Arc::new(InMemoryEventStore::new());
12
+ let event_log = Arc::new(EventLog::new(event_store.clone()));
13
+ let execution_id = ExecutionId::new();
14
+
15
+ let mut emitter = EventEmitter::with_persistence(event_log.clone(), execution_id.clone());
16
+ emitter.set_event_log(event_log, execution_id.clone());
17
+
18
+ // 2. Simulate Agentic Loop - Tool Execution
19
+ let step_id = StepId::new();
20
+ let tool_call_id = "call_12345";
21
+ let tool_name = "weather_tool";
22
+
23
+ // 2a. Tool Input Available (Should be mapped to ExecutionEventType::ToolCallStart)
24
+ let input_payload = serde_json::json!({ "city": "San Francisco" });
25
+ emitter.emit(StreamEvent::ToolInputAvailable {
26
+ tool_call_id: tool_call_id.to_string(),
27
+ tool_name: tool_name.to_string(),
28
+ input: input_payload.clone(),
29
+ });
30
+
31
+ // 2b. Tool Output Available (Should be mapped to ExecutionEventType::ToolCallEnd)
32
+ let output_payload = serde_json::json!({ "temp": 72, "conditions": "Sunny" });
33
+ emitter.emit(StreamEvent::ToolOutputAvailable {
34
+ tool_call_id: tool_call_id.to_string(),
35
+ output: output_payload.clone(),
36
+ });
37
+
38
+ // 3. Simulate Agentic Loop - Checkpoint
39
+ let checkpoint_id = "chk_12345";
40
+ emitter.emit(StreamEvent::CheckpointSaved {
41
+ execution_id: execution_id.as_str().to_string(),
42
+ step_id: Some(step_id.as_str().to_string()),
43
+ checkpoint_id: checkpoint_id.to_string(),
44
+ state_hash: "hash_abc123".to_string(),
45
+ timestamp: 1234567890,
46
+ });
47
+
48
+ // 4. Simulate Agentic Loop - Goal Evaluation
49
+ emitter.emit(StreamEvent::GoalEvaluated {
50
+ execution_id: execution_id.as_str().to_string(),
51
+ step_id: Some(step_id.as_str().to_string()),
52
+ goal_id: "goal_primary".to_string(),
53
+ status: "met".to_string(),
54
+ score: Some(1.0),
55
+ reason: Some("Task completed successfully".to_string()),
56
+ timestamp: 1234567890,
57
+ });
58
+
59
+ // Allow async persistence to complete
60
+ tokio::time::sleep(Duration::from_millis(100)).await;
61
+
62
+ // 5. Verification - Check EventStore (requires EventStore trait in scope)
63
+ let events = event_store
64
+ .get_by_execution(&execution_id)
65
+ .await
66
+ .expect("Failed to load events");
67
+
68
+ // We expect 4 events
69
+ assert_eq!(
70
+ events.len(),
71
+ 4,
72
+ "Expected 4 persisted events, found {}",
73
+ events.len()
74
+ );
75
+
76
+ // Verify Event 1: ToolCallStart
77
+ let event1 = &events[0].event;
78
+ assert!(
79
+ matches!(event1.event_type, ExecutionEventType::ToolCallStart),
80
+ "Event 1 should be ToolCallStart"
81
+ );
82
+ let p1 = event1.payload.as_ref().expect("Payload missing");
83
+ assert_eq!(p1["tool_call_id"], tool_call_id);
84
+ assert_eq!(p1["tool_name"], tool_name);
85
+ assert_eq!(p1["input"], input_payload);
86
+
87
+ // Verify Event 2: ToolCallEnd
88
+ let event2 = &events[1].event;
89
+ assert!(
90
+ matches!(event2.event_type, ExecutionEventType::ToolCallEnd),
91
+ "Event 2 should be ToolCallEnd"
92
+ );
93
+ let p2 = event2.payload.as_ref().expect("Payload missing");
94
+ assert_eq!(p2["tool_call_id"], tool_call_id);
95
+ assert_eq!(p2["output"], output_payload);
96
+
97
+ // Verify Event 3: CheckpointSaved
98
+ let event3 = &events[2].event;
99
+ assert!(
100
+ matches!(event3.event_type, ExecutionEventType::CheckpointSaved),
101
+ "Event 3 should be CheckpointSaved"
102
+ );
103
+ let p3 = event3.payload.as_ref().expect("Payload missing");
104
+ assert_eq!(p3["checkpoint_id"], checkpoint_id);
105
+ assert_eq!(p3["state_hash"], "hash_abc123");
106
+
107
+ // Verify Event 4: GoalEvaluated
108
+ let event4 = &events[3].event;
109
+ assert!(
110
+ matches!(event4.event_type, ExecutionEventType::GoalEvaluated),
111
+ "Event 4 should be GoalEvaluated"
112
+ );
113
+ let p4 = event4.payload.as_ref().expect("Payload missing");
114
+ assert_eq!(p4["goal_id"], "goal_primary");
115
+ assert_eq!(p4["status"], "met");
116
+ assert_eq!(p4["score"], 1.0);
117
+
118
+ println!("✅ All agentic loop events persisted correctly!");
119
+ }