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,1423 @@
1
+ //! Streaming Event Protocol
2
+ //!
3
+ //! Standardized event protocol for real-time streaming using SSE (Server-Sent Events).
4
+ //! All events use `data-*` prefix for AI SDK UI compatibility.
5
+ //!
6
+ //! ## Protocol Categories
7
+ //!
8
+ //! ### Standard Text Events (AI SDK compatible)
9
+ //! - `data-text-start` - Start of text generation
10
+ //! - `data-text-delta` - Incremental text chunk
11
+ //! - `data-text-end` - End of text generation
12
+ //!
13
+ //! ### Standard Step Events (AI SDK compatible)
14
+ //! - `data-start-step` - Step boundary start
15
+ //! - `data-finish-step` - Step boundary end
16
+ //!
17
+ //! ### Standard Tool Events (AI SDK compatible)
18
+ //! - `data-tool-input-start` - Tool call started
19
+ //! - `data-tool-input-delta` - Tool input streaming
20
+ //! - `data-tool-input-available` - Tool input complete
21
+ //! - `data-tool-output-available` - Tool result available
22
+ //!
23
+ //! ### Standard Lifecycle Events (AI SDK compatible)
24
+ //! - `data-start` - Message/stream start
25
+ //! - `data-finish` - Message/stream finish
26
+ //! - `data-error` - Error occurred
27
+ //!
28
+ //! ### Custom Execution Events (Enact-specific)
29
+ //! - `data-execution-start` - Execution started
30
+ //! - `data-execution-end` - Execution completed
31
+ //! - `data-execution-failed` - Execution failed
32
+ //! - `data-execution-paused` - Execution paused
33
+ //! - `data-execution-resumed` - Execution resumed
34
+ //! - `data-execution-cancelled` - Execution cancelled
35
+ //!
36
+ //! ### Custom Step Events (Enact-specific)
37
+ //! - `data-step-start` - Step started (with metadata)
38
+ //! - `data-step-end` - Step completed
39
+ //! - `data-step-failed` - Step failed
40
+ //!
41
+ //! ### Custom Artifact Events (Enact-specific)
42
+ //! - `data-artifact-created` - Artifact produced
43
+ //!
44
+ //! @see https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol
45
+
46
+ use super::event_logger::EventLog;
47
+ use crate::kernel::{ArtifactId, ExecutionError, ExecutionId, StepId, StepSourceType, StepType};
48
+ use crate::kernel::{ExecutionContext, ExecutionEvent, ExecutionEventType};
49
+ use futures::Stream;
50
+ use serde::{Deserialize, Serialize};
51
+ use std::pin::Pin;
52
+ use std::sync::Arc;
53
+
54
+ /// Event stream type - async stream of events
55
+ pub type EventStream = Pin<Box<dyn Stream<Item = StreamEvent> + Send>>;
56
+
57
+ // =============================================================================
58
+ // Stream Mode
59
+ // =============================================================================
60
+
61
+ /// StreamMode - Controls what events are included in the stream
62
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
63
+ #[serde(rename_all = "snake_case")]
64
+ pub enum StreamMode {
65
+ /// Full event stream - all events including token-level deltas
66
+ #[default]
67
+ Full,
68
+ /// Summary mode - only major events (step start/end, completion)
69
+ Summary,
70
+ /// Control only - only control signals (pause/resume/cancel)
71
+ ControlOnly,
72
+ /// Silent - no streaming (for batch processing)
73
+ Silent,
74
+ }
75
+
76
+ // =============================================================================
77
+ // Stream Event - Standardized Protocol with data-* prefix
78
+ // =============================================================================
79
+
80
+ /// StreamEvent - All streaming events with `data-*` prefix
81
+ ///
82
+ /// Events are tagged for direct SSE serialization.
83
+ /// UI adapters can consume these directly or transform as needed.
84
+ #[derive(Debug, Clone, Serialize, Deserialize)]
85
+ #[serde(tag = "type")]
86
+ pub enum StreamEvent {
87
+ // =========================================================================
88
+ // Standard AI SDK Events - Text Streaming
89
+ // =========================================================================
90
+ /// Text generation started
91
+ #[serde(rename = "data-text-start")]
92
+ TextStart {
93
+ id: String,
94
+ #[serde(skip_serializing_if = "Option::is_none")]
95
+ execution_id: Option<String>,
96
+ },
97
+
98
+ /// Incremental text chunk
99
+ #[serde(rename = "data-text-delta")]
100
+ TextDelta { id: String, delta: String },
101
+
102
+ /// Text generation ended
103
+ #[serde(rename = "data-text-end")]
104
+ TextEnd { id: String },
105
+
106
+ // =========================================================================
107
+ // Standard AI SDK Events - Step Control
108
+ // =========================================================================
109
+ /// Step boundary start (AI SDK step concept)
110
+ #[serde(rename = "data-start-step")]
111
+ StartStep {
112
+ #[serde(skip_serializing_if = "Option::is_none")]
113
+ step_id: Option<String>,
114
+ },
115
+
116
+ /// Step boundary end (AI SDK step concept)
117
+ #[serde(rename = "data-finish-step")]
118
+ FinishStep {
119
+ #[serde(skip_serializing_if = "Option::is_none")]
120
+ step_id: Option<String>,
121
+ },
122
+
123
+ // =========================================================================
124
+ // Standard AI SDK Events - Tool Calls
125
+ // =========================================================================
126
+ /// Tool input started
127
+ #[serde(rename = "data-tool-input-start")]
128
+ ToolInputStart {
129
+ tool_call_id: String,
130
+ tool_name: String,
131
+ },
132
+
133
+ /// Tool input delta (streaming input)
134
+ #[serde(rename = "data-tool-input-delta")]
135
+ ToolInputDelta {
136
+ tool_call_id: String,
137
+ input_text_delta: String,
138
+ },
139
+
140
+ /// Tool input available (complete input)
141
+ #[serde(rename = "data-tool-input-available")]
142
+ ToolInputAvailable {
143
+ tool_call_id: String,
144
+ tool_name: String,
145
+ input: serde_json::Value,
146
+ },
147
+
148
+ /// Tool output available
149
+ #[serde(rename = "data-tool-output-available")]
150
+ ToolOutputAvailable {
151
+ tool_call_id: String,
152
+ output: serde_json::Value,
153
+ },
154
+
155
+ // =========================================================================
156
+ // Standard AI SDK Events - Lifecycle
157
+ // =========================================================================
158
+ /// Stream/message start
159
+ #[serde(rename = "data-start")]
160
+ Start {
161
+ message_id: String,
162
+ #[serde(skip_serializing_if = "Option::is_none")]
163
+ execution_id: Option<String>,
164
+ },
165
+
166
+ /// Stream/message finish
167
+ #[serde(rename = "data-finish")]
168
+ Finish {
169
+ message_id: String,
170
+ #[serde(skip_serializing_if = "Option::is_none")]
171
+ final_output: Option<String>,
172
+ },
173
+
174
+ /// Error occurred
175
+ #[serde(rename = "data-error")]
176
+ Error { error: ExecutionError },
177
+
178
+ // =========================================================================
179
+ // Custom Enact Events - Execution Lifecycle
180
+ // =========================================================================
181
+ /// Execution started
182
+ #[serde(rename = "data-execution-start")]
183
+ ExecutionStart {
184
+ execution_id: String,
185
+ #[serde(skip_serializing_if = "Option::is_none")]
186
+ parent_id: Option<String>,
187
+ #[serde(skip_serializing_if = "Option::is_none")]
188
+ parent_type: Option<String>,
189
+ timestamp: i64,
190
+ },
191
+
192
+ /// Execution completed successfully
193
+ #[serde(rename = "data-execution-end")]
194
+ ExecutionEnd {
195
+ execution_id: String,
196
+ #[serde(skip_serializing_if = "Option::is_none")]
197
+ final_output: Option<String>,
198
+ duration_ms: u64,
199
+ timestamp: i64,
200
+ },
201
+
202
+ /// Execution failed
203
+ #[serde(rename = "data-execution-failed")]
204
+ ExecutionFailed {
205
+ execution_id: String,
206
+ error: ExecutionError,
207
+ timestamp: i64,
208
+ },
209
+
210
+ /// Execution paused
211
+ #[serde(rename = "data-execution-paused")]
212
+ ExecutionPaused {
213
+ execution_id: String,
214
+ reason: String,
215
+ timestamp: i64,
216
+ },
217
+
218
+ /// Execution resumed
219
+ #[serde(rename = "data-execution-resumed")]
220
+ ExecutionResumed {
221
+ execution_id: String,
222
+ timestamp: i64,
223
+ },
224
+
225
+ /// Execution cancelled
226
+ #[serde(rename = "data-execution-cancelled")]
227
+ ExecutionCancelled {
228
+ execution_id: String,
229
+ reason: String,
230
+ timestamp: i64,
231
+ },
232
+
233
+ // =========================================================================
234
+ // Custom Enact Events - Step Lifecycle
235
+ // =========================================================================
236
+ /// Step started (with full metadata)
237
+ #[serde(rename = "data-step-start")]
238
+ StepStart {
239
+ execution_id: String,
240
+ step_id: String,
241
+ step_type: String,
242
+ name: String,
243
+ timestamp: i64,
244
+ },
245
+
246
+ /// Step completed
247
+ #[serde(rename = "data-step-end")]
248
+ StepEnd {
249
+ execution_id: String,
250
+ step_id: String,
251
+ #[serde(skip_serializing_if = "Option::is_none")]
252
+ output: Option<String>,
253
+ duration_ms: u64,
254
+ timestamp: i64,
255
+ },
256
+
257
+ /// Step failed
258
+ #[serde(rename = "data-step-failed")]
259
+ StepFailed {
260
+ execution_id: String,
261
+ step_id: String,
262
+ error: ExecutionError,
263
+ timestamp: i64,
264
+ },
265
+
266
+ /// Step discovered during execution (dynamic discovery for agentic loops)
267
+ #[serde(rename = "data-step-discovered")]
268
+ StepDiscovered {
269
+ execution_id: String,
270
+ step_id: String,
271
+ #[serde(skip_serializing_if = "Option::is_none")]
272
+ discovered_by: Option<String>,
273
+ source_type: String,
274
+ reason: String,
275
+ depth: u32,
276
+ timestamp: i64,
277
+ },
278
+
279
+ // =========================================================================
280
+ // Custom Enact Events - Artifacts
281
+ // =========================================================================
282
+ /// Artifact created
283
+ #[serde(rename = "data-artifact-created")]
284
+ ArtifactCreated {
285
+ execution_id: String,
286
+ step_id: String,
287
+ artifact_id: String,
288
+ artifact_type: String,
289
+ timestamp: i64,
290
+ },
291
+
292
+ // =========================================================================
293
+ // Custom Enact Events - State
294
+ // =========================================================================
295
+ /// State snapshot captured
296
+ #[serde(rename = "data-state-snapshot")]
297
+ StateSnapshot {
298
+ execution_id: String,
299
+ #[serde(skip_serializing_if = "Option::is_none")]
300
+ step_id: Option<String>,
301
+ state: serde_json::Value,
302
+ timestamp: i64,
303
+ },
304
+
305
+ /// Checkpoint saved (Goal-driven persistence)
306
+ #[serde(rename = "data-checkpoint-saved")]
307
+ CheckpointSaved {
308
+ execution_id: String,
309
+ #[serde(skip_serializing_if = "Option::is_none")]
310
+ step_id: Option<String>,
311
+ checkpoint_id: String,
312
+ state_hash: String,
313
+ timestamp: i64,
314
+ },
315
+
316
+ /// Goal evaluated
317
+ #[serde(rename = "data-goal-evaluated")]
318
+ GoalEvaluated {
319
+ execution_id: String,
320
+ #[serde(skip_serializing_if = "Option::is_none")]
321
+ step_id: Option<String>,
322
+ goal_id: String,
323
+ status: String, // "met", "not_met", "progressing"
324
+ #[serde(skip_serializing_if = "Option::is_none")]
325
+ score: Option<f64>,
326
+ #[serde(skip_serializing_if = "Option::is_none")]
327
+ reason: Option<String>,
328
+ timestamp: i64,
329
+ },
330
+
331
+ // =========================================================================
332
+ // Custom Enact Events - Inbox (Mid-Execution Guidance)
333
+ // =========================================================================
334
+ /// Inbox message received (INV-INBOX-003: audit trail)
335
+ #[serde(rename = "data-inbox-message")]
336
+ InboxMessage {
337
+ execution_id: String,
338
+ message_id: String,
339
+ message_type: String,
340
+ timestamp: i64,
341
+ },
342
+
343
+ // =========================================================================
344
+ // Custom Enact Events - Decisions (Audit Trail)
345
+ // =========================================================================
346
+ /// Policy decision made (audit trail for tool policy evaluation)
347
+ #[serde(rename = "data-policy-decision")]
348
+ PolicyDecision {
349
+ execution_id: String,
350
+ #[serde(skip_serializing_if = "Option::is_none")]
351
+ step_id: Option<String>,
352
+ tool_name: String,
353
+ decision: String, // "allow", "deny", "warn"
354
+ #[serde(skip_serializing_if = "Option::is_none")]
355
+ reason: Option<String>,
356
+ timestamp: i64,
357
+ },
358
+ }
359
+
360
+ impl StreamEvent {
361
+ /// Get current timestamp in milliseconds
362
+ fn now() -> i64 {
363
+ std::time::SystemTime::now()
364
+ .duration_since(std::time::UNIX_EPOCH)
365
+ .unwrap_or_default()
366
+ .as_millis() as i64
367
+ }
368
+
369
+ /// Generate a unique ID for text/step tracking
370
+ fn generate_id() -> String {
371
+ use std::time::{SystemTime, UNIX_EPOCH};
372
+ let nanos = SystemTime::now()
373
+ .duration_since(UNIX_EPOCH)
374
+ .unwrap()
375
+ .as_nanos();
376
+ format!("id_{:x}", nanos)
377
+ }
378
+
379
+ // =========================================================================
380
+ // Standard AI SDK Event Factories
381
+ // =========================================================================
382
+
383
+ /// Create a text-start event
384
+ pub fn text_start(execution_id: Option<&ExecutionId>) -> Self {
385
+ Self::TextStart {
386
+ id: Self::generate_id(),
387
+ execution_id: execution_id.map(|e| e.as_str().to_string()),
388
+ }
389
+ }
390
+
391
+ /// Create a text-delta event
392
+ pub fn text_delta(id: impl Into<String>, delta: impl Into<String>) -> Self {
393
+ Self::TextDelta {
394
+ id: id.into(),
395
+ delta: delta.into(),
396
+ }
397
+ }
398
+
399
+ /// Create a text-end event
400
+ pub fn text_end(id: impl Into<String>) -> Self {
401
+ Self::TextEnd { id: id.into() }
402
+ }
403
+
404
+ /// Create a start event
405
+ pub fn start(message_id: impl Into<String>, execution_id: Option<&ExecutionId>) -> Self {
406
+ Self::Start {
407
+ message_id: message_id.into(),
408
+ execution_id: execution_id.map(|e| e.as_str().to_string()),
409
+ }
410
+ }
411
+
412
+ /// Create a finish event
413
+ pub fn finish(message_id: impl Into<String>, final_output: Option<String>) -> Self {
414
+ Self::Finish {
415
+ message_id: message_id.into(),
416
+ final_output,
417
+ }
418
+ }
419
+
420
+ /// Create an error event
421
+ pub fn error(error: ExecutionError) -> Self {
422
+ Self::Error { error }
423
+ }
424
+
425
+ /// Create a start-step event (AI SDK)
426
+ pub fn start_step(step_id: Option<&StepId>) -> Self {
427
+ Self::StartStep {
428
+ step_id: step_id.map(|s| s.as_str().to_string()),
429
+ }
430
+ }
431
+
432
+ /// Create a finish-step event (AI SDK)
433
+ pub fn finish_step(step_id: Option<&StepId>) -> Self {
434
+ Self::FinishStep {
435
+ step_id: step_id.map(|s| s.as_str().to_string()),
436
+ }
437
+ }
438
+
439
+ // =========================================================================
440
+ // Tool Event Factories
441
+ // =========================================================================
442
+
443
+ /// Create a tool-input-start event
444
+ pub fn tool_input_start(tool_call_id: impl Into<String>, tool_name: impl Into<String>) -> Self {
445
+ Self::ToolInputStart {
446
+ tool_call_id: tool_call_id.into(),
447
+ tool_name: tool_name.into(),
448
+ }
449
+ }
450
+
451
+ /// Create a tool-input-available event
452
+ pub fn tool_input_available(
453
+ tool_call_id: impl Into<String>,
454
+ tool_name: impl Into<String>,
455
+ input: serde_json::Value,
456
+ ) -> Self {
457
+ Self::ToolInputAvailable {
458
+ tool_call_id: tool_call_id.into(),
459
+ tool_name: tool_name.into(),
460
+ input,
461
+ }
462
+ }
463
+
464
+ /// Create a tool-output-available event
465
+ pub fn tool_output_available(
466
+ tool_call_id: impl Into<String>,
467
+ output: serde_json::Value,
468
+ ) -> Self {
469
+ Self::ToolOutputAvailable {
470
+ tool_call_id: tool_call_id.into(),
471
+ output,
472
+ }
473
+ }
474
+
475
+ // =========================================================================
476
+ // Execution Event Factories
477
+ // =========================================================================
478
+
479
+ /// Create an execution-start event
480
+ pub fn execution_start(execution_id: &ExecutionId) -> Self {
481
+ Self::ExecutionStart {
482
+ execution_id: execution_id.as_str().to_string(),
483
+ parent_id: None,
484
+ parent_type: None,
485
+ timestamp: Self::now(),
486
+ }
487
+ }
488
+
489
+ /// Create an execution-start event with parent
490
+ pub fn execution_start_with_parent(
491
+ execution_id: &ExecutionId,
492
+ parent_id: impl Into<String>,
493
+ parent_type: impl Into<String>,
494
+ ) -> Self {
495
+ Self::ExecutionStart {
496
+ execution_id: execution_id.as_str().to_string(),
497
+ parent_id: Some(parent_id.into()),
498
+ parent_type: Some(parent_type.into()),
499
+ timestamp: Self::now(),
500
+ }
501
+ }
502
+
503
+ /// Create an execution-end event
504
+ pub fn execution_end(
505
+ execution_id: &ExecutionId,
506
+ final_output: Option<String>,
507
+ duration_ms: u64,
508
+ ) -> Self {
509
+ Self::ExecutionEnd {
510
+ execution_id: execution_id.as_str().to_string(),
511
+ final_output,
512
+ duration_ms,
513
+ timestamp: Self::now(),
514
+ }
515
+ }
516
+
517
+ /// Create an execution-failed event
518
+ pub fn execution_failed(execution_id: &ExecutionId, error: ExecutionError) -> Self {
519
+ Self::ExecutionFailed {
520
+ execution_id: execution_id.as_str().to_string(),
521
+ error,
522
+ timestamp: Self::now(),
523
+ }
524
+ }
525
+
526
+ /// Create an execution-paused event
527
+ pub fn execution_paused(execution_id: &ExecutionId, reason: impl Into<String>) -> Self {
528
+ Self::ExecutionPaused {
529
+ execution_id: execution_id.as_str().to_string(),
530
+ reason: reason.into(),
531
+ timestamp: Self::now(),
532
+ }
533
+ }
534
+
535
+ /// Create an execution-resumed event
536
+ pub fn execution_resumed(execution_id: &ExecutionId) -> Self {
537
+ Self::ExecutionResumed {
538
+ execution_id: execution_id.as_str().to_string(),
539
+ timestamp: Self::now(),
540
+ }
541
+ }
542
+
543
+ /// Create an execution-cancelled event
544
+ pub fn execution_cancelled(execution_id: &ExecutionId, reason: impl Into<String>) -> Self {
545
+ Self::ExecutionCancelled {
546
+ execution_id: execution_id.as_str().to_string(),
547
+ reason: reason.into(),
548
+ timestamp: Self::now(),
549
+ }
550
+ }
551
+
552
+ // =========================================================================
553
+ // Step Event Factories
554
+ // =========================================================================
555
+
556
+ /// Create a step-start event
557
+ pub fn step_start(
558
+ execution_id: &ExecutionId,
559
+ step_id: &StepId,
560
+ step_type: StepType,
561
+ name: impl Into<String>,
562
+ ) -> Self {
563
+ Self::StepStart {
564
+ execution_id: execution_id.as_str().to_string(),
565
+ step_id: step_id.as_str().to_string(),
566
+ step_type: step_type.to_string(),
567
+ name: name.into(),
568
+ timestamp: Self::now(),
569
+ }
570
+ }
571
+
572
+ /// Create a step-end event
573
+ pub fn step_end(
574
+ execution_id: &ExecutionId,
575
+ step_id: &StepId,
576
+ output: Option<String>,
577
+ duration_ms: u64,
578
+ ) -> Self {
579
+ Self::StepEnd {
580
+ execution_id: execution_id.as_str().to_string(),
581
+ step_id: step_id.as_str().to_string(),
582
+ output,
583
+ duration_ms,
584
+ timestamp: Self::now(),
585
+ }
586
+ }
587
+
588
+ /// Create a step-failed event
589
+ pub fn step_failed(
590
+ execution_id: &ExecutionId,
591
+ step_id: &StepId,
592
+ error: ExecutionError,
593
+ ) -> Self {
594
+ Self::StepFailed {
595
+ execution_id: execution_id.as_str().to_string(),
596
+ step_id: step_id.as_str().to_string(),
597
+ error,
598
+ timestamp: Self::now(),
599
+ }
600
+ }
601
+
602
+ /// Create a step-discovered event
603
+ ///
604
+ /// Emitted when a new step is dynamically discovered during agentic execution.
605
+ /// This enables full audit trails for discovered steps (doc-30 Resource Discovery).
606
+ pub fn step_discovered(
607
+ execution_id: &ExecutionId,
608
+ step_id: &StepId,
609
+ discovered_by: Option<&StepId>,
610
+ source_type: StepSourceType,
611
+ reason: impl Into<String>,
612
+ depth: u32,
613
+ ) -> Self {
614
+ Self::StepDiscovered {
615
+ execution_id: execution_id.as_str().to_string(),
616
+ step_id: step_id.as_str().to_string(),
617
+ discovered_by: discovered_by.map(|s| s.as_str().to_string()),
618
+ source_type: format!("{:?}", source_type).to_lowercase(),
619
+ reason: reason.into(),
620
+ depth,
621
+ timestamp: Self::now(),
622
+ }
623
+ }
624
+
625
+ // =========================================================================
626
+ // Artifact Event Factories
627
+ // =========================================================================
628
+
629
+ /// Create an artifact-created event
630
+ pub fn artifact_created(
631
+ execution_id: &ExecutionId,
632
+ step_id: &StepId,
633
+ artifact_id: &ArtifactId,
634
+ artifact_type: impl Into<String>,
635
+ ) -> Self {
636
+ Self::ArtifactCreated {
637
+ execution_id: execution_id.as_str().to_string(),
638
+ step_id: step_id.as_str().to_string(),
639
+ artifact_id: artifact_id.as_str().to_string(),
640
+ artifact_type: artifact_type.into(),
641
+ timestamp: Self::now(),
642
+ }
643
+ }
644
+
645
+ // =========================================================================
646
+ // Inbox Event Factories (INV-INBOX-003: Audit Trail)
647
+ // =========================================================================
648
+
649
+ /// Create an inbox-message event for audit trail
650
+ ///
651
+ /// ## Invariant INV-INBOX-003
652
+ /// All inbox messages MUST emit events for audit trail.
653
+ pub fn inbox_message(
654
+ execution_id: &ExecutionId,
655
+ message_id: &str,
656
+ message_type: crate::inbox::InboxMessageType,
657
+ ) -> Self {
658
+ Self::InboxMessage {
659
+ execution_id: execution_id.as_str().to_string(),
660
+ message_id: message_id.to_string(),
661
+ message_type: format!("{:?}", message_type).to_lowercase(),
662
+ timestamp: Self::now(),
663
+ }
664
+ }
665
+
666
+ // =========================================================================
667
+ // Policy Decision Event Factories (Audit Trail)
668
+ // =========================================================================
669
+
670
+ /// Create a policy-decision event for audit trail
671
+ ///
672
+ /// Records tool policy evaluation decisions for compliance and audit.
673
+ pub fn policy_decision_allow(
674
+ execution_id: &ExecutionId,
675
+ step_id: Option<&StepId>,
676
+ tool_name: impl Into<String>,
677
+ ) -> Self {
678
+ Self::PolicyDecision {
679
+ execution_id: execution_id.as_str().to_string(),
680
+ step_id: step_id.map(|s| s.as_str().to_string()),
681
+ tool_name: tool_name.into(),
682
+ decision: "allow".to_string(),
683
+ reason: None,
684
+ timestamp: Self::now(),
685
+ }
686
+ }
687
+
688
+ /// Create a policy-decision deny event
689
+ pub fn policy_decision_deny(
690
+ execution_id: &ExecutionId,
691
+ step_id: Option<&StepId>,
692
+ tool_name: impl Into<String>,
693
+ reason: impl Into<String>,
694
+ ) -> Self {
695
+ Self::PolicyDecision {
696
+ execution_id: execution_id.as_str().to_string(),
697
+ step_id: step_id.map(|s| s.as_str().to_string()),
698
+ tool_name: tool_name.into(),
699
+ decision: "deny".to_string(),
700
+ reason: Some(reason.into()),
701
+ timestamp: Self::now(),
702
+ }
703
+ }
704
+
705
+ /// Create a policy-decision warn event
706
+ pub fn policy_decision_warn(
707
+ execution_id: &ExecutionId,
708
+ step_id: Option<&StepId>,
709
+ tool_name: impl Into<String>,
710
+ message: impl Into<String>,
711
+ ) -> Self {
712
+ Self::PolicyDecision {
713
+ execution_id: execution_id.as_str().to_string(),
714
+ step_id: step_id.map(|s| s.as_str().to_string()),
715
+ tool_name: tool_name.into(),
716
+ decision: "warn".to_string(),
717
+ reason: Some(message.into()),
718
+ timestamp: Self::now(),
719
+ }
720
+ }
721
+
722
+ // =========================================================================
723
+ // Utility Methods
724
+ // =========================================================================
725
+
726
+ /// Check if this is a control event (pause/resume/cancel)
727
+ pub fn is_control_event(&self) -> bool {
728
+ matches!(
729
+ self,
730
+ Self::ExecutionPaused { .. }
731
+ | Self::ExecutionResumed { .. }
732
+ | Self::ExecutionCancelled { .. }
733
+ )
734
+ }
735
+
736
+ /// Check if this is a delta/streaming event
737
+ pub fn is_delta_event(&self) -> bool {
738
+ matches!(self, Self::TextDelta { .. } | Self::ToolInputDelta { .. })
739
+ }
740
+
741
+ /// Check if this is a summary-level event (not a delta)
742
+ pub fn is_summary_event(&self) -> bool {
743
+ !self.is_delta_event()
744
+ }
745
+
746
+ /// Serialize to SSE format: `data: {...}\n\n`
747
+ pub fn to_sse(&self) -> String {
748
+ format!(
749
+ "data: {}\n\n",
750
+ serde_json::to_string(self).unwrap_or_default()
751
+ )
752
+ }
753
+
754
+ /// Create a [DONE] termination signal
755
+ pub fn done() -> String {
756
+ "data: [DONE]\n\n".to_string()
757
+ }
758
+ }
759
+
760
+ // =============================================================================
761
+ // Event Emitter
762
+ // =============================================================================
763
+
764
+ /// Event emitter - collects and filters events during execution
765
+ ///
766
+ /// Supports optional persistence to EventLog (INV-PERSIST-002: Event Immutability).
767
+ /// When an EventLog is configured, events are persisted before being added to
768
+ /// the in-memory buffer.
769
+ #[derive(Clone)]
770
+ pub struct EventEmitter {
771
+ events: std::sync::Arc<std::sync::Mutex<Vec<StreamEvent>>>,
772
+ mode: StreamMode,
773
+ /// Optional event log for persistence
774
+ event_log: Option<Arc<EventLog>>,
775
+ /// Execution context for persisted events
776
+ execution_context: Option<ExecutionContext>,
777
+ }
778
+
779
+ impl std::fmt::Debug for EventEmitter {
780
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
781
+ f.debug_struct("EventEmitter")
782
+ .field(
783
+ "events_count",
784
+ &self.events.lock().map(|e| e.len()).unwrap_or(0),
785
+ )
786
+ .field("mode", &self.mode)
787
+ .field("has_event_log", &self.event_log.is_some())
788
+ .finish()
789
+ }
790
+ }
791
+
792
+ impl Default for EventEmitter {
793
+ fn default() -> Self {
794
+ Self::new()
795
+ }
796
+ }
797
+
798
+ impl EventEmitter {
799
+ /// Create a new EventEmitter
800
+ pub fn new() -> Self {
801
+ Self {
802
+ events: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
803
+ mode: StreamMode::Full,
804
+ event_log: None,
805
+ execution_context: None,
806
+ }
807
+ }
808
+
809
+ /// Create a new EventEmitter with a specific mode
810
+ pub fn with_mode(mode: StreamMode) -> Self {
811
+ Self {
812
+ events: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
813
+ mode,
814
+ event_log: None,
815
+ execution_context: None,
816
+ }
817
+ }
818
+
819
+ /// Create an EventEmitter with persistence support
820
+ ///
821
+ /// Events will be persisted to the EventLog before being added to the buffer.
822
+ /// This ensures durability (INV-PERSIST-002).
823
+ pub fn with_persistence(event_log: Arc<EventLog>, execution_id: ExecutionId) -> Self {
824
+ Self {
825
+ events: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
826
+ mode: StreamMode::Full,
827
+ event_log: Some(event_log),
828
+ execution_context: Some(ExecutionContext::new(execution_id)),
829
+ }
830
+ }
831
+
832
+ /// Set the stream mode
833
+ pub fn set_mode(&mut self, mode: StreamMode) {
834
+ self.mode = mode;
835
+ }
836
+
837
+ /// Enable persistence with an event log
838
+ pub fn set_event_log(&mut self, event_log: Arc<EventLog>, execution_id: ExecutionId) {
839
+ self.event_log = Some(event_log);
840
+ self.execution_context = Some(ExecutionContext::new(execution_id));
841
+ }
842
+
843
+ /// Emit an event (filtered by mode)
844
+ ///
845
+ /// If persistence is configured, the event is persisted to the EventLog
846
+ /// BEFORE being added to the in-memory buffer. This ensures durability.
847
+ pub fn emit(&self, event: StreamEvent) {
848
+ let should_emit = match self.mode {
849
+ StreamMode::Full => true,
850
+ StreamMode::Summary => event.is_summary_event(),
851
+ StreamMode::ControlOnly => event.is_control_event(),
852
+ StreamMode::Silent => false,
853
+ };
854
+
855
+ if should_emit {
856
+ // Persist to EventLog if configured (async, best-effort for now)
857
+ // In production, this should be awaited before proceeding
858
+ if let (Some(event_log), Some(ctx)) = (&self.event_log, &self.execution_context) {
859
+ if let Some(exec_event) = self.to_execution_event(&event, ctx) {
860
+ let log = Arc::clone(event_log);
861
+ let evt = exec_event;
862
+ // Spawn persistence task (best-effort for non-async emit)
863
+ tokio::spawn(async move {
864
+ if let Err(e) = log.append(evt).await {
865
+ tracing::warn!("Failed to persist event: {}", e);
866
+ }
867
+ });
868
+ }
869
+ }
870
+
871
+ if let Ok(mut events) = self.events.lock() {
872
+ events.push(event);
873
+ }
874
+ }
875
+ }
876
+
877
+ /// Convert StreamEvent to ExecutionEvent for persistence
878
+ ///
879
+ /// Maps StreamEvent variants to ExecutionEventType.
880
+ /// Some events are skipped as they don't need persistence (deltas, intermediates).
881
+ fn to_execution_event(
882
+ &self,
883
+ stream_event: &StreamEvent,
884
+ ctx: &ExecutionContext,
885
+ ) -> Option<ExecutionEvent> {
886
+ let event_type = match stream_event {
887
+ // Execution lifecycle events
888
+ StreamEvent::ExecutionStart { .. } => ExecutionEventType::ExecutionStart,
889
+ StreamEvent::ExecutionEnd { .. } => ExecutionEventType::ExecutionEnd,
890
+ StreamEvent::ExecutionFailed { .. } => ExecutionEventType::ExecutionFailed,
891
+ StreamEvent::ExecutionPaused { .. } => ExecutionEventType::ControlPause,
892
+ StreamEvent::ExecutionResumed { .. } => ExecutionEventType::ControlResume,
893
+ StreamEvent::ExecutionCancelled { .. } => ExecutionEventType::ExecutionCancelled,
894
+
895
+ // Step lifecycle events
896
+ StreamEvent::StepStart { .. } => ExecutionEventType::StepStart,
897
+ StreamEvent::StepEnd { .. } => ExecutionEventType::StepEnd,
898
+ StreamEvent::StepFailed { .. } => ExecutionEventType::StepFailed,
899
+ StreamEvent::StepDiscovered { .. } => ExecutionEventType::StepDiscovered,
900
+
901
+ // Artifact events
902
+ StreamEvent::ArtifactCreated { .. } => ExecutionEventType::ArtifactCreated,
903
+
904
+ // State snapshots
905
+ StreamEvent::StateSnapshot { .. } => ExecutionEventType::StateSnapshot,
906
+
907
+ // Inbox messages (INV-INBOX-003: audit trail)
908
+ StreamEvent::InboxMessage { .. } => ExecutionEventType::InboxMessage,
909
+
910
+ // Policy decisions (audit trail for tool policy evaluation)
911
+ StreamEvent::PolicyDecision { .. } => ExecutionEventType::DecisionMade,
912
+
913
+ StreamEvent::CheckpointSaved { .. } => ExecutionEventType::CheckpointSaved,
914
+ StreamEvent::GoalEvaluated { .. } => ExecutionEventType::GoalEvaluated,
915
+
916
+ // Tool execution (persistence now enabled)
917
+ StreamEvent::ToolInputAvailable { .. } => ExecutionEventType::ToolCallStart,
918
+ StreamEvent::ToolOutputAvailable { .. } => ExecutionEventType::ToolCallEnd,
919
+
920
+ // Skip events that don't need persistence:
921
+ // - Text deltas (streaming intermediates)
922
+ // - Tool input deltas (streaming intermediates)
923
+ // - Generic start/finish (covered by execution lifecycle)
924
+ // - Step boundary markers (covered by step lifecycle)
925
+ StreamEvent::Start { .. }
926
+ | StreamEvent::Finish { .. }
927
+ | StreamEvent::Error { .. }
928
+ | StreamEvent::StartStep { .. }
929
+ | StreamEvent::FinishStep { .. }
930
+ | StreamEvent::TextStart { .. }
931
+ | StreamEvent::TextDelta { .. }
932
+ | StreamEvent::TextEnd { .. }
933
+ | StreamEvent::ToolInputStart { .. }
934
+ | StreamEvent::ToolInputDelta { .. } => return None,
935
+ };
936
+
937
+ let mut context = ctx.clone();
938
+ let mut payload: Option<serde_json::Value> = None;
939
+ let mut duration_ms: Option<u64> = None;
940
+
941
+ match stream_event {
942
+ StreamEvent::ExecutionEnd {
943
+ final_output,
944
+ duration_ms: dur,
945
+ ..
946
+ } => {
947
+ duration_ms = Some(*dur);
948
+ if let Some(output) = final_output {
949
+ payload = Some(serde_json::json!({ "output": output }));
950
+ }
951
+ }
952
+ StreamEvent::ExecutionFailed { error, .. } => {
953
+ payload = serde_json::to_value(error)
954
+ .ok()
955
+ .map(|err| serde_json::json!({ "error": err }));
956
+ }
957
+ StreamEvent::ExecutionCancelled { reason, .. }
958
+ | StreamEvent::ExecutionPaused { reason, .. } => {
959
+ payload = Some(serde_json::json!({ "reason": reason }));
960
+ }
961
+ StreamEvent::StepStart {
962
+ step_id,
963
+ step_type,
964
+ name,
965
+ ..
966
+ } => {
967
+ context = context.with_step(StepId::from_string(step_id));
968
+ payload = Some(serde_json::json!({ "step_type": step_type, "name": name }));
969
+ }
970
+ StreamEvent::StepEnd {
971
+ step_id,
972
+ output,
973
+ duration_ms: dur,
974
+ ..
975
+ } => {
976
+ context = context.with_step(StepId::from_string(step_id));
977
+ duration_ms = Some(*dur);
978
+ if let Some(out) = output {
979
+ payload = Some(serde_json::json!({ "output": out }));
980
+ }
981
+ }
982
+ StreamEvent::StepFailed { step_id, error, .. } => {
983
+ context = context.with_step(StepId::from_string(step_id));
984
+ payload = serde_json::to_value(error)
985
+ .ok()
986
+ .map(|err| serde_json::json!({ "error": err }));
987
+ }
988
+ StreamEvent::StepDiscovered {
989
+ step_id,
990
+ discovered_by,
991
+ source_type,
992
+ reason,
993
+ depth,
994
+ ..
995
+ } => {
996
+ context = context.with_step(StepId::from_string(step_id));
997
+ payload = Some(serde_json::json!({
998
+ "discovered_by": discovered_by,
999
+ "source_type": source_type,
1000
+ "reason": reason,
1001
+ "depth": depth,
1002
+ }));
1003
+ }
1004
+ StreamEvent::ArtifactCreated {
1005
+ step_id,
1006
+ artifact_id,
1007
+ artifact_type,
1008
+ ..
1009
+ } => {
1010
+ context = context
1011
+ .with_step(StepId::from_string(step_id))
1012
+ .with_artifact(ArtifactId::from_string(artifact_id));
1013
+ payload = Some(serde_json::json!({ "artifact_type": artifact_type }));
1014
+ }
1015
+ StreamEvent::StateSnapshot { step_id, state, .. } => {
1016
+ if let Some(step) = step_id {
1017
+ context = context.with_step(StepId::from_string(step));
1018
+ }
1019
+ payload = Some(state.clone());
1020
+ }
1021
+ StreamEvent::InboxMessage {
1022
+ message_id,
1023
+ message_type,
1024
+ ..
1025
+ } => {
1026
+ payload = Some(serde_json::json!({
1027
+ "message_id": message_id,
1028
+ "message_type": message_type
1029
+ }));
1030
+ }
1031
+ StreamEvent::PolicyDecision {
1032
+ step_id,
1033
+ tool_name,
1034
+ decision,
1035
+ reason,
1036
+ ..
1037
+ } => {
1038
+ if let Some(step) = step_id {
1039
+ context = context.with_step(StepId::from_string(step));
1040
+ }
1041
+ payload = Some(serde_json::json!({
1042
+ "tool_name": tool_name,
1043
+ "decision": decision,
1044
+ "tool_name": tool_name,
1045
+ "decision": decision,
1046
+ "reason": reason,
1047
+ }));
1048
+ }
1049
+ StreamEvent::ToolInputAvailable {
1050
+ tool_call_id,
1051
+ tool_name,
1052
+ input,
1053
+ ..
1054
+ } => {
1055
+ payload = Some(serde_json::json!({
1056
+ "tool_call_id": tool_call_id,
1057
+ "tool_name": tool_name,
1058
+ "input": input,
1059
+ }));
1060
+ }
1061
+ StreamEvent::ToolOutputAvailable {
1062
+ tool_call_id,
1063
+ output,
1064
+ ..
1065
+ } => {
1066
+ payload = Some(serde_json::json!({
1067
+ "tool_call_id": tool_call_id,
1068
+ "output": output,
1069
+ }));
1070
+ }
1071
+ StreamEvent::CheckpointSaved {
1072
+ checkpoint_id,
1073
+ step_id,
1074
+ state_hash,
1075
+ ..
1076
+ } => {
1077
+ if let Some(step) = step_id {
1078
+ context = context.with_step(StepId::from_string(step));
1079
+ }
1080
+ payload = Some(serde_json::json!({
1081
+ "checkpoint_id": checkpoint_id,
1082
+ "state_hash": state_hash,
1083
+ }));
1084
+ }
1085
+ StreamEvent::GoalEvaluated {
1086
+ goal_id,
1087
+ step_id,
1088
+ status,
1089
+ score,
1090
+ reason,
1091
+ ..
1092
+ } => {
1093
+ if let Some(step) = step_id {
1094
+ context = context.with_step(StepId::from_string(step));
1095
+ }
1096
+ payload = Some(serde_json::json!({
1097
+ "goal_id": goal_id,
1098
+ "status": status,
1099
+ "score": score,
1100
+ "reason": reason,
1101
+ }));
1102
+ }
1103
+ StreamEvent::ExecutionStart { .. } => {}
1104
+ _ => {}
1105
+ }
1106
+
1107
+ let mut event = ExecutionEvent::new(event_type, context);
1108
+ if let Some(ms) = duration_ms {
1109
+ event.duration_ms = Some(ms);
1110
+ }
1111
+ if let Some(data) = payload {
1112
+ event = event.with_payload(data);
1113
+ }
1114
+
1115
+ Some(event)
1116
+ }
1117
+
1118
+ /// Emit an event unconditionally (ignores mode)
1119
+ pub fn emit_force(&self, event: StreamEvent) {
1120
+ if let Ok(mut events) = self.events.lock() {
1121
+ events.push(event);
1122
+ }
1123
+ }
1124
+
1125
+ /// Get all collected events
1126
+ pub fn drain(&self) -> Vec<StreamEvent> {
1127
+ if let Ok(mut events) = self.events.lock() {
1128
+ std::mem::take(&mut *events)
1129
+ } else {
1130
+ vec![]
1131
+ }
1132
+ }
1133
+
1134
+ /// Get the current stream mode
1135
+ pub fn mode(&self) -> StreamMode {
1136
+ self.mode
1137
+ }
1138
+ }
1139
+
1140
+ #[cfg(test)]
1141
+ mod tests {
1142
+ use super::*;
1143
+ use crate::kernel::{ExecutionId, StepId, StepType};
1144
+
1145
+ // ============ StreamMode Tests ============
1146
+
1147
+ #[test]
1148
+ fn test_stream_mode_default() {
1149
+ assert_eq!(StreamMode::default(), StreamMode::Full);
1150
+ }
1151
+
1152
+ #[test]
1153
+ fn test_stream_mode_variants() {
1154
+ let modes = vec![
1155
+ StreamMode::Full,
1156
+ StreamMode::Summary,
1157
+ StreamMode::ControlOnly,
1158
+ StreamMode::Silent,
1159
+ ];
1160
+ assert_eq!(modes.len(), 4);
1161
+ }
1162
+
1163
+ // ============ StreamEvent Utility Tests ============
1164
+
1165
+ #[test]
1166
+ fn test_stream_event_is_control_event() {
1167
+ let exec_id = ExecutionId::new();
1168
+
1169
+ // Control events
1170
+ assert!(StreamEvent::execution_paused(&exec_id, "paused").is_control_event());
1171
+ assert!(StreamEvent::execution_resumed(&exec_id).is_control_event());
1172
+ assert!(StreamEvent::execution_cancelled(&exec_id, "cancelled").is_control_event());
1173
+
1174
+ // Non-control events
1175
+ assert!(!StreamEvent::execution_start(&exec_id).is_control_event());
1176
+ assert!(!StreamEvent::text_start(None).is_control_event());
1177
+ }
1178
+
1179
+ #[test]
1180
+ fn test_stream_event_is_delta_event() {
1181
+ // Delta events
1182
+ let delta = StreamEvent::text_delta("id1", "chunk");
1183
+ assert!(delta.is_delta_event());
1184
+
1185
+ // Non-delta events
1186
+ let start = StreamEvent::text_start(None);
1187
+ assert!(!start.is_delta_event());
1188
+ }
1189
+
1190
+ #[test]
1191
+ fn test_stream_event_is_summary_event() {
1192
+ // Summary events (non-delta)
1193
+ let start = StreamEvent::text_start(None);
1194
+ assert!(start.is_summary_event());
1195
+
1196
+ // Delta events are not summary
1197
+ let delta = StreamEvent::text_delta("id1", "chunk");
1198
+ assert!(!delta.is_summary_event());
1199
+ }
1200
+
1201
+ #[test]
1202
+ fn test_stream_event_to_sse() {
1203
+ let event = StreamEvent::text_end("test-id");
1204
+ let sse = event.to_sse();
1205
+
1206
+ assert!(sse.starts_with("data: "));
1207
+ assert!(sse.ends_with("\n\n"));
1208
+ assert!(sse.contains("test-id"));
1209
+ }
1210
+
1211
+ #[test]
1212
+ fn test_stream_event_done() {
1213
+ let done = StreamEvent::done();
1214
+ assert_eq!(done, "data: [DONE]\n\n");
1215
+ }
1216
+
1217
+ // ============ StreamEvent Factory Tests ============
1218
+
1219
+ #[test]
1220
+ fn test_stream_event_text_factories() {
1221
+ let exec_id = ExecutionId::new();
1222
+
1223
+ let start = StreamEvent::text_start(Some(&exec_id));
1224
+ assert!(matches!(start, StreamEvent::TextStart { .. }));
1225
+
1226
+ let delta = StreamEvent::text_delta("id1", "hello");
1227
+ assert!(matches!(delta, StreamEvent::TextDelta { delta, .. } if delta == "hello"));
1228
+
1229
+ let end = StreamEvent::text_end("id1");
1230
+ assert!(matches!(end, StreamEvent::TextEnd { .. }));
1231
+ }
1232
+
1233
+ #[test]
1234
+ fn test_stream_event_execution_factories() {
1235
+ let exec_id = ExecutionId::new();
1236
+
1237
+ let start = StreamEvent::execution_start(&exec_id);
1238
+ assert!(matches!(start, StreamEvent::ExecutionStart { .. }));
1239
+
1240
+ let end = StreamEvent::execution_end(&exec_id, Some("output".to_string()), 100);
1241
+ assert!(matches!(
1242
+ end,
1243
+ StreamEvent::ExecutionEnd {
1244
+ duration_ms: 100,
1245
+ ..
1246
+ }
1247
+ ));
1248
+
1249
+ use crate::kernel::ExecutionError;
1250
+ let error = ExecutionError::kernel_internal("error message");
1251
+ let failed = StreamEvent::execution_failed(&exec_id, error);
1252
+ assert!(matches!(failed, StreamEvent::ExecutionFailed { .. }));
1253
+
1254
+ let paused = StreamEvent::execution_paused(&exec_id, "reason");
1255
+ assert!(matches!(paused, StreamEvent::ExecutionPaused { .. }));
1256
+
1257
+ let resumed = StreamEvent::execution_resumed(&exec_id);
1258
+ assert!(matches!(resumed, StreamEvent::ExecutionResumed { .. }));
1259
+
1260
+ let cancelled = StreamEvent::execution_cancelled(&exec_id, "cancel reason");
1261
+ assert!(matches!(cancelled, StreamEvent::ExecutionCancelled { .. }));
1262
+ }
1263
+
1264
+ #[test]
1265
+ fn test_stream_event_step_factories() {
1266
+ let exec_id = ExecutionId::new();
1267
+ let step_id = StepId::new();
1268
+
1269
+ let start =
1270
+ StreamEvent::step_start(&exec_id, &step_id, StepType::FunctionNode, "test_step");
1271
+ assert!(matches!(start, StreamEvent::StepStart { .. }));
1272
+
1273
+ let end = StreamEvent::step_end(&exec_id, &step_id, Some("output".to_string()), 50);
1274
+ assert!(matches!(
1275
+ end,
1276
+ StreamEvent::StepEnd {
1277
+ duration_ms: 50,
1278
+ ..
1279
+ }
1280
+ ));
1281
+
1282
+ use crate::kernel::ExecutionError;
1283
+ let error = ExecutionError::kernel_internal("step error");
1284
+ let failed = StreamEvent::step_failed(&exec_id, &step_id, error);
1285
+ assert!(matches!(failed, StreamEvent::StepFailed { .. }));
1286
+ }
1287
+
1288
+ #[test]
1289
+ fn test_stream_event_tool_factories() {
1290
+ let input_start = StreamEvent::tool_input_start("call-123", "web_search");
1291
+ assert!(
1292
+ matches!(input_start, StreamEvent::ToolInputStart { tool_name, .. } if tool_name == "web_search")
1293
+ );
1294
+
1295
+ let input_avail = StreamEvent::tool_input_available(
1296
+ "call-123",
1297
+ "web_search",
1298
+ serde_json::json!({"q": "test"}),
1299
+ );
1300
+ assert!(matches!(
1301
+ input_avail,
1302
+ StreamEvent::ToolInputAvailable { .. }
1303
+ ));
1304
+
1305
+ let output_avail =
1306
+ StreamEvent::tool_output_available("call-123", serde_json::json!({"result": "ok"}));
1307
+ assert!(matches!(
1308
+ output_avail,
1309
+ StreamEvent::ToolOutputAvailable { .. }
1310
+ ));
1311
+ }
1312
+
1313
+ // ============ EventEmitter Tests ============
1314
+
1315
+ #[test]
1316
+ fn test_event_emitter_new() {
1317
+ let emitter = EventEmitter::new();
1318
+ assert_eq!(emitter.mode(), StreamMode::Full);
1319
+ }
1320
+
1321
+ #[test]
1322
+ fn test_event_emitter_with_mode() {
1323
+ let emitter = EventEmitter::with_mode(StreamMode::Summary);
1324
+ assert_eq!(emitter.mode(), StreamMode::Summary);
1325
+ }
1326
+
1327
+ #[test]
1328
+ fn test_event_emitter_set_mode() {
1329
+ let mut emitter = EventEmitter::new();
1330
+ emitter.set_mode(StreamMode::Silent);
1331
+ assert_eq!(emitter.mode(), StreamMode::Silent);
1332
+ }
1333
+
1334
+ #[test]
1335
+ fn test_event_emitter_emit_and_drain() {
1336
+ let emitter = EventEmitter::new();
1337
+ let exec_id = ExecutionId::new();
1338
+
1339
+ emitter.emit(StreamEvent::execution_start(&exec_id));
1340
+ emitter.emit(StreamEvent::execution_end(&exec_id, None, 100));
1341
+
1342
+ let events = emitter.drain();
1343
+ assert_eq!(events.len(), 2);
1344
+
1345
+ // Drain should clear events
1346
+ let events_after = emitter.drain();
1347
+ assert!(events_after.is_empty());
1348
+ }
1349
+
1350
+ #[test]
1351
+ fn test_event_emitter_mode_full() {
1352
+ let emitter = EventEmitter::with_mode(StreamMode::Full);
1353
+
1354
+ // All events should be emitted
1355
+ emitter.emit(StreamEvent::text_delta("id", "chunk"));
1356
+ emitter.emit(StreamEvent::text_end("id"));
1357
+
1358
+ let events = emitter.drain();
1359
+ assert_eq!(events.len(), 2);
1360
+ }
1361
+
1362
+ #[test]
1363
+ fn test_event_emitter_mode_summary() {
1364
+ let emitter = EventEmitter::with_mode(StreamMode::Summary);
1365
+
1366
+ // Delta events should be filtered out
1367
+ emitter.emit(StreamEvent::text_delta("id", "chunk"));
1368
+ emitter.emit(StreamEvent::text_end("id"));
1369
+
1370
+ let events = emitter.drain();
1371
+ assert_eq!(events.len(), 1); // Only text_end (summary event)
1372
+ }
1373
+
1374
+ #[test]
1375
+ fn test_event_emitter_mode_control_only() {
1376
+ let emitter = EventEmitter::with_mode(StreamMode::ControlOnly);
1377
+ let exec_id = ExecutionId::new();
1378
+
1379
+ // Non-control events should be filtered out
1380
+ emitter.emit(StreamEvent::execution_start(&exec_id));
1381
+ emitter.emit(StreamEvent::execution_paused(&exec_id, "test"));
1382
+
1383
+ let events = emitter.drain();
1384
+ assert_eq!(events.len(), 1); // Only paused (control event)
1385
+ }
1386
+
1387
+ #[test]
1388
+ fn test_event_emitter_mode_silent() {
1389
+ let emitter = EventEmitter::with_mode(StreamMode::Silent);
1390
+ let exec_id = ExecutionId::new();
1391
+
1392
+ // All events should be filtered out
1393
+ emitter.emit(StreamEvent::execution_start(&exec_id));
1394
+ emitter.emit(StreamEvent::execution_paused(&exec_id, "test"));
1395
+
1396
+ let events = emitter.drain();
1397
+ assert!(events.is_empty());
1398
+ }
1399
+
1400
+ #[test]
1401
+ fn test_event_emitter_emit_force() {
1402
+ let emitter = EventEmitter::with_mode(StreamMode::Silent);
1403
+ let exec_id = ExecutionId::new();
1404
+
1405
+ // Force emit should bypass mode filter
1406
+ emitter.emit_force(StreamEvent::execution_start(&exec_id));
1407
+
1408
+ let events = emitter.drain();
1409
+ assert_eq!(events.len(), 1);
1410
+ }
1411
+
1412
+ #[test]
1413
+ fn test_event_emitter_serialization() {
1414
+ use crate::kernel::ExecutionError;
1415
+ let error = ExecutionError::kernel_internal("Test error").with_code("ERR_CODE".to_string());
1416
+ let event = StreamEvent::error(error);
1417
+ let json = serde_json::to_string(&event).unwrap();
1418
+
1419
+ assert!(json.contains("data-error"));
1420
+ assert!(json.contains("Test error"));
1421
+ assert!(json.contains("ERR_CODE"));
1422
+ }
1423
+ }