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,831 @@
1
+ //! Execution Model - Runtime instance types for graph/agent execution
2
+ //!
3
+ //! This module defines the core model types:
4
+ //! - `Execution`: One run of a blueprint (graph, agent, workflow)
5
+ //! - `Step`: A distinct action within an execution
6
+ //!
7
+ //! These are the **model types** that hold execution state.
8
+ //! The **engine** (ExecutionKernel) uses these models to manage execution.
9
+ //!
10
+ //! ## ⚠️ CRITICAL INVARIANT: Data + Bookkeeping Only
11
+ //!
12
+ //! **This module MUST NOT contain execution logic or side effects.**
13
+ //!
14
+ //! This module is strictly for:
15
+ //! - Data structures (Execution, Step)
16
+ //! - Bookkeeping methods (getters, setters, state queries)
17
+ //!
18
+ //! This module MUST NEVER:
19
+ //! - Execute logic (no business logic, no decision-making)
20
+ //! - Call providers (no external service calls)
21
+ //! - Embed policy checks (policy belongs in kernel::enforcement)
22
+ //! - Perform side effects (no I/O, no mutations beyond self)
23
+ //!
24
+ //! If you find yourself adding execution logic here, it belongs in:
25
+ //! - `kernel::reducer` (for state transitions)
26
+ //! - `kernel::enforcement` (for policy checks)
27
+ //! - `kernel::execution_kernel` (for orchestration)
28
+ //!
29
+ //! ## Error Handling (feat-02)
30
+ //!
31
+ //! All errors are represented using `ExecutionError` which provides:
32
+ //! - Deterministic retry policies
33
+ //! - Structured error categories
34
+ //! - HTTP status code mapping
35
+ //! - Idempotency tracking
36
+
37
+ use super::error::ExecutionError;
38
+ use super::execution_state::{ExecutionState, StepState};
39
+ use super::ids::{CallableType, ExecutionId, ParentLink, StepId, StepSource, StepType, TenantId};
40
+ use serde::{Deserialize, Serialize};
41
+ use std::collections::HashMap;
42
+ use std::time::Instant;
43
+
44
+ /// Execution - one run of a blueprint
45
+ #[derive(Debug)]
46
+ pub struct Execution {
47
+ /// Unique execution ID
48
+ pub id: ExecutionId,
49
+ /// Tenant ID (REQUIRED for audit trail and multi-tenant isolation)
50
+ /// This is set from TenantContext when the kernel is created.
51
+ pub tenant_id: Option<TenantId>,
52
+ /// Current state in the lifecycle
53
+ pub state: ExecutionState,
54
+ /// Parent execution (if this is a sub-execution)
55
+ pub parent: Option<ParentLink>,
56
+ /// All steps in this execution
57
+ pub steps: HashMap<StepId, Step>,
58
+ /// Ordered list of step IDs (for replay)
59
+ pub step_order: Vec<StepId>,
60
+ /// Schema version hash (for replay validation)
61
+ pub schema_version: Option<String>,
62
+ /// Start time
63
+ pub started_at: Option<Instant>,
64
+ /// End time
65
+ pub ended_at: Option<Instant>,
66
+ /// Final output (if completed)
67
+ pub output: Option<String>,
68
+ /// Structured error (if failed) - see feat-02 Error Taxonomy
69
+ pub error: Option<ExecutionError>,
70
+ }
71
+
72
+ impl Execution {
73
+ /// Create a new execution
74
+ pub fn new() -> Self {
75
+ Self {
76
+ id: ExecutionId::new(),
77
+ tenant_id: None,
78
+ state: ExecutionState::Created,
79
+ parent: None,
80
+ steps: HashMap::new(),
81
+ step_order: Vec::new(),
82
+ schema_version: None,
83
+ started_at: None,
84
+ ended_at: None,
85
+ output: None,
86
+ error: None,
87
+ }
88
+ }
89
+
90
+ /// Create a new execution with a specific ID
91
+ pub fn with_id(id: ExecutionId) -> Self {
92
+ Self {
93
+ id,
94
+ tenant_id: None,
95
+ state: ExecutionState::Created,
96
+ parent: None,
97
+ steps: HashMap::new(),
98
+ step_order: Vec::new(),
99
+ schema_version: None,
100
+ started_at: None,
101
+ ended_at: None,
102
+ output: None,
103
+ error: None,
104
+ }
105
+ }
106
+
107
+ /// Create a new execution with tenant ID
108
+ pub fn with_tenant(tenant_id: TenantId) -> Self {
109
+ Self {
110
+ id: ExecutionId::new(),
111
+ tenant_id: Some(tenant_id),
112
+ state: ExecutionState::Created,
113
+ parent: None,
114
+ steps: HashMap::new(),
115
+ step_order: Vec::new(),
116
+ schema_version: None,
117
+ started_at: None,
118
+ ended_at: None,
119
+ output: None,
120
+ error: None,
121
+ }
122
+ }
123
+
124
+ /// Create a child execution
125
+ /// Inherits tenant_id from parent for multi-tenant isolation
126
+ pub fn child(&self) -> Self {
127
+ let mut child = Self::new();
128
+ child.parent = Some(ParentLink::execution(self.id.clone()));
129
+ child.tenant_id = self.tenant_id.clone(); // Inherit tenant from parent
130
+ child
131
+ }
132
+
133
+ /// Set schema version for replay validation
134
+ pub fn with_schema_version(mut self, version: impl Into<String>) -> Self {
135
+ self.schema_version = Some(version.into());
136
+ self
137
+ }
138
+
139
+ /// Get a step by ID
140
+ pub fn get_step(&self, id: &StepId) -> Option<&Step> {
141
+ self.steps.get(id)
142
+ }
143
+
144
+ /// Get a mutable step by ID
145
+ pub fn get_step_mut(&mut self, id: &StepId) -> Option<&mut Step> {
146
+ self.steps.get_mut(id)
147
+ }
148
+
149
+ /// Add a new step
150
+ pub fn add_step(&mut self, step: Step) {
151
+ self.step_order.push(step.id.clone());
152
+ self.steps.insert(step.id.clone(), step);
153
+ }
154
+
155
+ /// Get execution duration in milliseconds
156
+ pub fn duration_ms(&self) -> Option<u64> {
157
+ match (self.started_at, self.ended_at) {
158
+ (Some(start), Some(end)) => Some(end.duration_since(start).as_millis() as u64),
159
+ (Some(start), None) => Some(start.elapsed().as_millis() as u64),
160
+ _ => None,
161
+ }
162
+ }
163
+
164
+ /// Check if execution is in a terminal state
165
+ pub fn is_terminal(&self) -> bool {
166
+ self.state.is_terminal()
167
+ }
168
+ }
169
+
170
+ impl Default for Execution {
171
+ fn default() -> Self {
172
+ Self::new()
173
+ }
174
+ }
175
+
176
+ /// Step - a distinct action within an execution
177
+ ///
178
+ /// @see packages/enact-schemas/src/streaming.schemas.ts - stepEventDataSchema
179
+ #[derive(Debug, Clone, Serialize, Deserialize)]
180
+ #[serde(rename_all = "camelCase")]
181
+ pub struct Step {
182
+ /// Unique step ID
183
+ #[serde(rename = "stepId")]
184
+ pub id: StepId,
185
+ /// Parent step (for nested operations)
186
+ #[serde(rename = "parentStepId")]
187
+ pub parent_step_id: Option<StepId>,
188
+ /// Step type
189
+ pub step_type: StepType,
190
+ /// Step name/label
191
+ pub name: String,
192
+ /// Current state (matches `state` in TypeScript schema)
193
+ pub state: StepState,
194
+ /// Input to this step
195
+ pub input: Option<String>,
196
+ /// Output from this step
197
+ pub output: Option<String>,
198
+ /// Structured error (if failed) - see feat-02 Error Taxonomy
199
+ pub error: Option<ExecutionError>,
200
+ /// Duration in milliseconds
201
+ pub duration_ms: Option<u64>,
202
+ /// Timestamp when step started (unix millis)
203
+ pub started_at: Option<i64>,
204
+ /// Timestamp when step ended (unix millis)
205
+ pub ended_at: Option<i64>,
206
+ /// Source/origin of this step (how/why it was created)
207
+ #[serde(skip_serializing_if = "Option::is_none")]
208
+ pub source: Option<StepSource>,
209
+ /// Callable ID (stable identifier for billing/traceability)
210
+ /// Unlike `name` which can change, this provides a stable reference.
211
+ #[serde(skip_serializing_if = "Option::is_none")]
212
+ pub callable_id: Option<String>,
213
+ /// Callable type (for billing differentiation and audit trails)
214
+ #[serde(skip_serializing_if = "Option::is_none")]
215
+ pub callable_type: Option<CallableType>,
216
+ }
217
+
218
+ impl Step {
219
+ /// Create a new step
220
+ pub fn new(step_type: StepType, name: impl Into<String>) -> Self {
221
+ Self {
222
+ id: StepId::new(),
223
+ parent_step_id: None,
224
+ step_type,
225
+ name: name.into(),
226
+ state: StepState::Pending,
227
+ input: None,
228
+ output: None,
229
+ error: None,
230
+ duration_ms: None,
231
+ started_at: None,
232
+ ended_at: None,
233
+ source: None,
234
+ callable_id: None,
235
+ callable_type: None,
236
+ }
237
+ }
238
+
239
+ /// Create a nested step under a parent
240
+ pub fn nested(parent_id: &StepId, step_type: StepType, name: impl Into<String>) -> Self {
241
+ let mut step = Self::new(step_type, name);
242
+ step.parent_step_id = Some(parent_id.clone());
243
+ step
244
+ }
245
+
246
+ /// Set input
247
+ pub fn with_input(mut self, input: impl Into<String>) -> Self {
248
+ self.input = Some(input.into());
249
+ self
250
+ }
251
+
252
+ /// Set source/origin of this step
253
+ pub fn with_source(mut self, source: StepSource) -> Self {
254
+ self.source = Some(source);
255
+ self
256
+ }
257
+
258
+ /// Set callable info (for billing/traceability)
259
+ ///
260
+ /// Unlike `name` which can change, callable_id provides a stable reference
261
+ /// for cost attribution and audit trails.
262
+ pub fn with_callable(
263
+ mut self,
264
+ callable_id: impl Into<String>,
265
+ callable_type: CallableType,
266
+ ) -> Self {
267
+ self.callable_id = Some(callable_id.into());
268
+ self.callable_type = Some(callable_type);
269
+ self
270
+ }
271
+ }
272
+
273
+ #[cfg(test)]
274
+ mod tests {
275
+ use super::super::execution_state::WaitReason;
276
+ use super::super::ids::StepSourceType;
277
+ use super::*;
278
+
279
+ // =========================================================================
280
+ // Execution Tests
281
+ // =========================================================================
282
+
283
+ #[test]
284
+ fn test_execution_new() {
285
+ let exec = Execution::new();
286
+ assert!(exec.id.as_str().starts_with("exec_"));
287
+ assert_eq!(exec.state, ExecutionState::Created);
288
+ assert!(exec.parent.is_none());
289
+ assert!(exec.steps.is_empty());
290
+ assert!(exec.step_order.is_empty());
291
+ assert!(exec.schema_version.is_none());
292
+ assert!(exec.started_at.is_none());
293
+ assert!(exec.ended_at.is_none());
294
+ assert!(exec.output.is_none());
295
+ assert!(exec.error.is_none());
296
+ }
297
+
298
+ #[test]
299
+ fn test_execution_with_id() {
300
+ let id = ExecutionId::from_string("exec_custom_id");
301
+ let exec = Execution::with_id(id.clone());
302
+ assert_eq!(exec.id.as_str(), "exec_custom_id");
303
+ assert_eq!(exec.state, ExecutionState::Created);
304
+ }
305
+
306
+ #[test]
307
+ fn test_execution_child() {
308
+ let parent = Execution::new();
309
+ let child = parent.child();
310
+
311
+ assert!(child.parent.is_some());
312
+ let parent_link = child.parent.unwrap();
313
+ assert_eq!(parent_link.parent_id, parent.id.as_str());
314
+ }
315
+
316
+ #[test]
317
+ fn test_execution_with_schema_version() {
318
+ let exec = Execution::new().with_schema_version("v1.0.0");
319
+ assert_eq!(exec.schema_version, Some("v1.0.0".to_string()));
320
+ }
321
+
322
+ #[test]
323
+ fn test_execution_with_schema_version_owned_string() {
324
+ let exec = Execution::new().with_schema_version(String::from("v2.0.0"));
325
+ assert_eq!(exec.schema_version, Some("v2.0.0".to_string()));
326
+ }
327
+
328
+ #[test]
329
+ fn test_execution_add_step() {
330
+ let mut exec = Execution::new();
331
+ let step = Step::new(StepType::LlmNode, "test_step");
332
+ let step_id = step.id.clone();
333
+
334
+ exec.add_step(step);
335
+
336
+ assert_eq!(exec.steps.len(), 1);
337
+ assert_eq!(exec.step_order.len(), 1);
338
+ assert!(exec.steps.contains_key(&step_id));
339
+ }
340
+
341
+ #[test]
342
+ fn test_execution_get_step() {
343
+ let mut exec = Execution::new();
344
+ let step = Step::new(StepType::ToolNode, "get_step_test");
345
+ let step_id = step.id.clone();
346
+ exec.add_step(step);
347
+
348
+ let retrieved = exec.get_step(&step_id);
349
+ assert!(retrieved.is_some());
350
+ assert_eq!(retrieved.unwrap().name, "get_step_test");
351
+ }
352
+
353
+ #[test]
354
+ fn test_execution_get_step_not_found() {
355
+ let exec = Execution::new();
356
+ let nonexistent_id = StepId::from_string("step_nonexistent");
357
+ assert!(exec.get_step(&nonexistent_id).is_none());
358
+ }
359
+
360
+ #[test]
361
+ fn test_execution_get_step_mut() {
362
+ let mut exec = Execution::new();
363
+ let step = Step::new(StepType::FunctionNode, "mutable_step");
364
+ let step_id = step.id.clone();
365
+ exec.add_step(step);
366
+
367
+ let step_mut = exec.get_step_mut(&step_id);
368
+ assert!(step_mut.is_some());
369
+
370
+ step_mut.unwrap().output = Some("modified".to_string());
371
+
372
+ let step = exec.get_step(&step_id).unwrap();
373
+ assert_eq!(step.output, Some("modified".to_string()));
374
+ }
375
+
376
+ #[test]
377
+ fn test_execution_step_order_preserved() {
378
+ let mut exec = Execution::new();
379
+ let step1 = Step::new(StepType::LlmNode, "step1");
380
+ let step2 = Step::new(StepType::ToolNode, "step2");
381
+ let step3 = Step::new(StepType::FunctionNode, "step3");
382
+
383
+ let id1 = step1.id.clone();
384
+ let id2 = step2.id.clone();
385
+ let id3 = step3.id.clone();
386
+
387
+ exec.add_step(step1);
388
+ exec.add_step(step2);
389
+ exec.add_step(step3);
390
+
391
+ assert_eq!(exec.step_order[0], id1);
392
+ assert_eq!(exec.step_order[1], id2);
393
+ assert_eq!(exec.step_order[2], id3);
394
+ }
395
+
396
+ #[test]
397
+ fn test_execution_duration_ms_not_started() {
398
+ let exec = Execution::new();
399
+ assert!(exec.duration_ms().is_none());
400
+ }
401
+
402
+ #[test]
403
+ fn test_execution_duration_ms_started_not_ended() {
404
+ let mut exec = Execution::new();
405
+ exec.started_at = Some(Instant::now());
406
+ std::thread::sleep(std::time::Duration::from_millis(10));
407
+
408
+ let duration = exec.duration_ms();
409
+ assert!(duration.is_some());
410
+ assert!(duration.unwrap() >= 10);
411
+ }
412
+
413
+ #[test]
414
+ fn test_execution_duration_ms_completed() {
415
+ let mut exec = Execution::new();
416
+ let start = Instant::now();
417
+ std::thread::sleep(std::time::Duration::from_millis(20));
418
+ let end = Instant::now();
419
+
420
+ exec.started_at = Some(start);
421
+ exec.ended_at = Some(end);
422
+
423
+ let duration = exec.duration_ms();
424
+ assert!(duration.is_some());
425
+ assert!(duration.unwrap() >= 20);
426
+ }
427
+
428
+ #[test]
429
+ fn test_execution_is_terminal_created() {
430
+ let exec = Execution::new();
431
+ assert!(!exec.is_terminal());
432
+ }
433
+
434
+ #[test]
435
+ fn test_execution_is_terminal_running() {
436
+ let mut exec = Execution::new();
437
+ exec.state = ExecutionState::Running;
438
+ assert!(!exec.is_terminal());
439
+ }
440
+
441
+ #[test]
442
+ fn test_execution_is_terminal_completed() {
443
+ let mut exec = Execution::new();
444
+ exec.state = ExecutionState::Completed;
445
+ assert!(exec.is_terminal());
446
+ }
447
+
448
+ #[test]
449
+ fn test_execution_is_terminal_failed() {
450
+ let mut exec = Execution::new();
451
+ exec.state = ExecutionState::Failed;
452
+ assert!(exec.is_terminal());
453
+ }
454
+
455
+ #[test]
456
+ fn test_execution_is_terminal_cancelled() {
457
+ let mut exec = Execution::new();
458
+ exec.state = ExecutionState::Cancelled;
459
+ assert!(exec.is_terminal());
460
+ }
461
+
462
+ #[test]
463
+ fn test_execution_is_terminal_paused() {
464
+ let mut exec = Execution::new();
465
+ exec.state = ExecutionState::Paused;
466
+ assert!(!exec.is_terminal());
467
+ }
468
+
469
+ #[test]
470
+ fn test_execution_is_terminal_waiting() {
471
+ let mut exec = Execution::new();
472
+ exec.state = ExecutionState::Waiting(WaitReason::Approval);
473
+ assert!(!exec.is_terminal());
474
+ }
475
+
476
+ #[test]
477
+ fn test_execution_default() {
478
+ let exec: Execution = Default::default();
479
+ assert!(exec.id.as_str().starts_with("exec_"));
480
+ assert_eq!(exec.state, ExecutionState::Created);
481
+ }
482
+
483
+ // =========================================================================
484
+ // Step Tests
485
+ // =========================================================================
486
+
487
+ #[test]
488
+ fn test_step_new() {
489
+ let step = Step::new(StepType::LlmNode, "test_step");
490
+ assert!(step.id.as_str().starts_with("step_"));
491
+ assert_eq!(step.step_type, StepType::LlmNode);
492
+ assert_eq!(step.name, "test_step");
493
+ assert_eq!(step.state, StepState::Pending);
494
+ assert!(step.parent_step_id.is_none());
495
+ assert!(step.input.is_none());
496
+ assert!(step.output.is_none());
497
+ assert!(step.error.is_none());
498
+ assert!(step.duration_ms.is_none());
499
+ }
500
+
501
+ #[test]
502
+ fn test_step_new_all_types() {
503
+ let types = vec![
504
+ StepType::LlmNode,
505
+ StepType::GraphNode,
506
+ StepType::ToolNode,
507
+ StepType::FunctionNode,
508
+ StepType::RouterNode,
509
+ StepType::BranchNode,
510
+ StepType::LoopNode,
511
+ ];
512
+
513
+ for step_type in types {
514
+ let step = Step::new(step_type.clone(), "test");
515
+ assert_eq!(step.step_type, step_type);
516
+ }
517
+ }
518
+
519
+ #[test]
520
+ fn test_step_nested() {
521
+ let parent_id = StepId::from_string("step_parent");
522
+ let step = Step::nested(&parent_id, StepType::ToolNode, "nested_step");
523
+
524
+ assert!(step.parent_step_id.is_some());
525
+ assert_eq!(step.parent_step_id.unwrap().as_str(), "step_parent");
526
+ assert_eq!(step.step_type, StepType::ToolNode);
527
+ assert_eq!(step.name, "nested_step");
528
+ }
529
+
530
+ #[test]
531
+ fn test_step_with_input() {
532
+ let step = Step::new(StepType::LlmNode, "input_step").with_input("Hello, AI!");
533
+
534
+ assert!(step.input.is_some());
535
+ assert_eq!(step.input.unwrap(), "Hello, AI!");
536
+ }
537
+
538
+ #[test]
539
+ fn test_step_with_input_owned_string() {
540
+ let step =
541
+ Step::new(StepType::LlmNode, "input_step").with_input(String::from("Owned input"));
542
+
543
+ assert!(step.input.is_some());
544
+ assert_eq!(step.input.unwrap(), "Owned input");
545
+ }
546
+
547
+ #[test]
548
+ fn test_step_clone() {
549
+ let step = Step::new(StepType::FunctionNode, "cloneable").with_input("input data");
550
+ let cloned = step.clone();
551
+
552
+ assert_eq!(step.id.as_str(), cloned.id.as_str());
553
+ assert_eq!(step.name, cloned.name);
554
+ assert_eq!(step.input, cloned.input);
555
+ }
556
+
557
+ #[test]
558
+ fn test_step_serde() {
559
+ let step = Step::new(StepType::GraphNode, "serializable").with_input("input");
560
+
561
+ let json = serde_json::to_string(&step).unwrap();
562
+ let parsed: Step = serde_json::from_str(&json).unwrap();
563
+
564
+ assert_eq!(step.name, parsed.name);
565
+ assert_eq!(step.step_type, parsed.step_type);
566
+ assert_eq!(step.input, parsed.input);
567
+ }
568
+
569
+ #[test]
570
+ fn test_step_serde_field_names() {
571
+ // Verify JSON field names match TypeScript schema (camelCase)
572
+ let step = Step::new(StepType::LlmNode, "test_step").with_input("test input");
573
+
574
+ let json = serde_json::to_string(&step).unwrap();
575
+
576
+ // Check that camelCase field names are used
577
+ assert!(json.contains("\"stepId\""), "Should have stepId field");
578
+ assert!(json.contains("\"stepType\""), "Should have stepType field");
579
+ assert!(json.contains("\"state\""), "Should have state field");
580
+ assert!(
581
+ json.contains("\"durationMs\""),
582
+ "Should have durationMs field"
583
+ );
584
+ assert!(
585
+ json.contains("\"startedAt\""),
586
+ "Should have startedAt field"
587
+ );
588
+ assert!(json.contains("\"endedAt\""), "Should have endedAt field");
589
+
590
+ // Verify no snake_case field names
591
+ assert!(
592
+ !json.contains("\"step_id\""),
593
+ "Should NOT have step_id field"
594
+ );
595
+ assert!(
596
+ !json.contains("\"step_type\""),
597
+ "Should NOT have step_type field"
598
+ );
599
+ assert!(
600
+ !json.contains("\"duration_ms\""),
601
+ "Should NOT have duration_ms field"
602
+ );
603
+ assert!(
604
+ !json.contains("\"started_at\""),
605
+ "Should NOT have started_at field"
606
+ );
607
+ assert!(
608
+ !json.contains("\"ended_at\""),
609
+ "Should NOT have ended_at field"
610
+ );
611
+ }
612
+
613
+ #[test]
614
+ fn test_step_timestamps() {
615
+ let mut step = Step::new(StepType::ToolNode, "timestamped");
616
+ let now = chrono::Utc::now().timestamp_millis();
617
+
618
+ step.started_at = Some(now);
619
+ step.ended_at = Some(now + 1000);
620
+ step.duration_ms = Some(1000);
621
+
622
+ assert_eq!(step.started_at, Some(now));
623
+ assert_eq!(step.ended_at, Some(now + 1000));
624
+ assert_eq!(step.duration_ms, Some(1000));
625
+ }
626
+
627
+ #[test]
628
+ fn test_step_state_modifications() {
629
+ let mut step = Step::new(StepType::LlmNode, "state_step");
630
+
631
+ assert_eq!(step.state, StepState::Pending);
632
+
633
+ step.state = StepState::Running;
634
+ assert_eq!(step.state, StepState::Running);
635
+
636
+ step.state = StepState::Completed;
637
+ step.output = Some("Result".to_string());
638
+ assert_eq!(step.state, StepState::Completed);
639
+ assert_eq!(step.output, Some("Result".to_string()));
640
+ }
641
+
642
+ #[test]
643
+ fn test_step_error_handling() {
644
+ let mut step = Step::new(StepType::ToolNode, "error_step");
645
+
646
+ step.state = StepState::Failed;
647
+ step.error = Some(ExecutionError::kernel_internal("Test error"));
648
+
649
+ assert!(step.error.is_some());
650
+ }
651
+
652
+ // =========================================================================
653
+ // Integration Tests
654
+ // =========================================================================
655
+
656
+ #[test]
657
+ fn test_execution_with_multiple_steps() {
658
+ let mut exec = Execution::new();
659
+ exec.state = ExecutionState::Running;
660
+ exec.started_at = Some(Instant::now());
661
+
662
+ // Add multiple steps
663
+ for i in 0..5 {
664
+ let step = Step::new(StepType::FunctionNode, format!("step_{}", i))
665
+ .with_input(format!("input_{}", i));
666
+ exec.add_step(step);
667
+ }
668
+
669
+ assert_eq!(exec.steps.len(), 5);
670
+ assert_eq!(exec.step_order.len(), 5);
671
+
672
+ // Verify all steps are accessible
673
+ for step_id in &exec.step_order {
674
+ assert!(exec.get_step(step_id).is_some());
675
+ }
676
+ }
677
+
678
+ #[test]
679
+ fn test_nested_execution_structure() {
680
+ let root = Execution::new();
681
+ let child1 = root.child();
682
+ let child2 = root.child();
683
+
684
+ // Both children should have the same parent
685
+ assert!(child1.parent.is_some());
686
+ assert!(child2.parent.is_some());
687
+ assert_eq!(
688
+ child1.parent.as_ref().unwrap().parent_id,
689
+ child2.parent.as_ref().unwrap().parent_id
690
+ );
691
+
692
+ // But different IDs
693
+ assert_ne!(child1.id.as_str(), child2.id.as_str());
694
+ }
695
+
696
+ #[test]
697
+ fn test_nested_steps_structure() {
698
+ let parent_step = Step::new(StepType::GraphNode, "parent");
699
+ let parent_id = parent_step.id.clone();
700
+
701
+ let child1 = Step::nested(&parent_id, StepType::LlmNode, "child1");
702
+ let child2 = Step::nested(&parent_id, StepType::ToolNode, "child2");
703
+
704
+ assert_eq!(
705
+ child1.parent_step_id.as_ref().unwrap().as_str(),
706
+ parent_id.as_str()
707
+ );
708
+ assert_eq!(
709
+ child2.parent_step_id.as_ref().unwrap().as_str(),
710
+ parent_id.as_str()
711
+ );
712
+ }
713
+
714
+ // =========================================================================
715
+ // Callable Tracking Tests (for billing/traceability)
716
+ // =========================================================================
717
+
718
+ #[test]
719
+ fn test_step_with_callable() {
720
+ let step = Step::new(StepType::GraphNode, "Research Agent")
721
+ .with_callable("research-agent-v2", CallableType::Agent);
722
+
723
+ assert!(step.callable_id.is_some());
724
+ assert_eq!(step.callable_id.unwrap(), "research-agent-v2");
725
+ assert!(step.callable_type.is_some());
726
+ assert_eq!(step.callable_type.unwrap(), CallableType::Agent);
727
+ }
728
+
729
+ #[test]
730
+ fn test_step_callable_serde() {
731
+ let step = Step::new(StepType::GraphNode, "Chat Handler")
732
+ .with_callable("chat-handler", CallableType::Chat);
733
+
734
+ let json = serde_json::to_string(&step).unwrap();
735
+ let parsed: Step = serde_json::from_str(&json).unwrap();
736
+
737
+ assert_eq!(parsed.callable_id, Some("chat-handler".to_string()));
738
+ assert_eq!(parsed.callable_type, Some(CallableType::Chat));
739
+
740
+ // Verify camelCase field names
741
+ assert!(
742
+ json.contains("\"callableId\""),
743
+ "Should have callableId field"
744
+ );
745
+ assert!(
746
+ json.contains("\"callableType\""),
747
+ "Should have callableType field"
748
+ );
749
+ }
750
+
751
+ #[test]
752
+ fn test_step_callable_none_not_serialized() {
753
+ let step = Step::new(StepType::LlmNode, "No Callable Info");
754
+
755
+ let json = serde_json::to_string(&step).unwrap();
756
+
757
+ // Optional None fields should not appear in JSON (skip_serializing_if)
758
+ assert!(
759
+ !json.contains("callableId"),
760
+ "Should NOT serialize None callableId"
761
+ );
762
+ assert!(
763
+ !json.contains("callableType"),
764
+ "Should NOT serialize None callableType"
765
+ );
766
+ }
767
+
768
+ #[test]
769
+ fn test_callable_type_display() {
770
+ assert_eq!(format!("{}", CallableType::Completion), "completion");
771
+ assert_eq!(format!("{}", CallableType::Chat), "chat");
772
+ assert_eq!(format!("{}", CallableType::Agent), "agent");
773
+ assert_eq!(format!("{}", CallableType::Workflow), "workflow");
774
+ assert_eq!(format!("{}", CallableType::Background), "background");
775
+ assert_eq!(format!("{}", CallableType::Composite), "composite");
776
+ assert_eq!(format!("{}", CallableType::Tool), "tool");
777
+ assert_eq!(format!("{}", CallableType::Custom), "custom");
778
+ }
779
+
780
+ #[test]
781
+ fn test_callable_type_default() {
782
+ let default_type = CallableType::default();
783
+ assert_eq!(default_type, CallableType::Agent);
784
+ }
785
+
786
+ #[test]
787
+ fn test_callable_type_serde_all_variants() {
788
+ let variants = vec![
789
+ CallableType::Completion,
790
+ CallableType::Chat,
791
+ CallableType::Agent,
792
+ CallableType::Workflow,
793
+ CallableType::Background,
794
+ CallableType::Composite,
795
+ CallableType::Tool,
796
+ CallableType::Custom,
797
+ ];
798
+
799
+ for variant in variants {
800
+ let json = serde_json::to_string(&variant).unwrap();
801
+ let parsed: CallableType = serde_json::from_str(&json).unwrap();
802
+ assert_eq!(parsed, variant);
803
+ }
804
+ }
805
+
806
+ #[test]
807
+ fn test_step_chained_builders() {
808
+ // Test that all builders can be chained
809
+ let parent_id = StepId::from_string("step_parent");
810
+ let step = Step::new(StepType::GraphNode, "Full Step")
811
+ .with_input("User request")
812
+ .with_callable("research-agent", CallableType::Agent)
813
+ .with_source(StepSource {
814
+ source_type: StepSourceType::Discovered,
815
+ triggered_by: Some("step_123".to_string()),
816
+ reason: Some("LLM suggested sub-task".to_string()),
817
+ depth: Some(1),
818
+ spawn_mode: None,
819
+ });
820
+
821
+ assert_eq!(step.name, "Full Step");
822
+ assert_eq!(step.input, Some("User request".to_string()));
823
+ assert_eq!(step.callable_id, Some("research-agent".to_string()));
824
+ assert_eq!(step.callable_type, Some(CallableType::Agent));
825
+ assert!(step.source.is_some());
826
+ assert_eq!(
827
+ step.source.as_ref().unwrap().source_type,
828
+ StepSourceType::Discovered
829
+ );
830
+ }
831
+ }