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,2086 @@
1
+ //! Execution & Telemetry IDs - Graph-centric observability identifiers
2
+ //!
3
+ //! This module defines the canonical ID types for the enact-core runtime,
4
+ //! aligned with the Execution/Step/Artifact hierarchy.
5
+ //!
6
+ //! ## ID Format
7
+ //! All IDs use prefixed KSUID (K-Sortable Unique IDentifier):
8
+ //! - `exec_[27 alphanumeric chars]` - ExecutionId
9
+ //! - `step_[27 alphanumeric chars]` - StepId
10
+ //! - `artifact_[27 alphanumeric chars]` - ArtifactId
11
+ //!
12
+ //! ## Hierarchy
13
+ //! ```text
14
+ //! ExecutionId (The Run)
15
+ //! ├── StepId (Type: ToolNode) -> Artifacts
16
+ //! ├── StepId (Type: LlmNode) -> Artifacts
17
+ //! └── StepId (Type: GraphNode "Sub-Agent")
18
+ //! └── ExecutionId (Nested run)
19
+ //! └── StepId...
20
+ //! ```
21
+ //!
22
+ //! @see docs/TECHNICAL/01-EXECUTION-TELEMETRY.md
23
+
24
+ use serde::{Deserialize, Serialize};
25
+ use std::fmt;
26
+ use svix_ksuid::{Ksuid, KsuidLike};
27
+
28
+ /// Generate a new KSUID
29
+ fn new_ksuid() -> String {
30
+ Ksuid::new(None, None).to_string()
31
+ }
32
+
33
+ // =============================================================================
34
+ // ExecutionId - One run of a blueprint (Agent/Graph/Workflow)
35
+ // =============================================================================
36
+
37
+ /// ExecutionId - Identifies one run of a blueprint
38
+ ///
39
+ /// This is the primary unit of billing and audit.
40
+ /// Produces an append-only stream of ExecutionEvents.
41
+ ///
42
+ /// Format: `exec_[27-char KSUID]`
43
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44
+ pub struct ExecutionId(String);
45
+
46
+ impl ExecutionId {
47
+ /// Create a new ExecutionId with auto-generated KSUID
48
+ pub fn new() -> Self {
49
+ Self(format!("exec_{}", new_ksuid()))
50
+ }
51
+
52
+ /// Create from an existing string (useful for deserialization)
53
+ pub fn from_string(s: impl Into<String>) -> Self {
54
+ Self(s.into())
55
+ }
56
+
57
+ /// Get the ID as a string slice
58
+ pub fn as_str(&self) -> &str {
59
+ &self.0
60
+ }
61
+ }
62
+
63
+ impl Default for ExecutionId {
64
+ fn default() -> Self {
65
+ Self::new()
66
+ }
67
+ }
68
+
69
+ impl fmt::Display for ExecutionId {
70
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71
+ write!(f, "{}", self.0)
72
+ }
73
+ }
74
+
75
+ impl From<String> for ExecutionId {
76
+ fn from(s: String) -> Self {
77
+ Self(s)
78
+ }
79
+ }
80
+
81
+ impl From<&str> for ExecutionId {
82
+ fn from(s: &str) -> Self {
83
+ Self(s.to_string())
84
+ }
85
+ }
86
+
87
+ /// RunId - Alias for ExecutionId
88
+ pub type RunId = ExecutionId;
89
+
90
+ // =============================================================================
91
+ // StepId - A distinct action within an execution
92
+ // =============================================================================
93
+
94
+ /// StepId - Identifies a distinct action within an execution
95
+ ///
96
+ /// In TUI/CLI, appears as a "Sub-Agent" or "Action" performing work.
97
+ /// Internally, this is a graph node execution.
98
+ ///
99
+ /// Format: `step_[27-char KSUID]`
100
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
101
+ pub struct StepId(String);
102
+
103
+ impl StepId {
104
+ /// Create a new StepId with auto-generated KSUID
105
+ pub fn new() -> Self {
106
+ Self(format!("step_{}", new_ksuid()))
107
+ }
108
+
109
+ /// Create from an existing string
110
+ pub fn from_string(s: impl Into<String>) -> Self {
111
+ Self(s.into())
112
+ }
113
+
114
+ /// Get the ID as a string slice
115
+ pub fn as_str(&self) -> &str {
116
+ &self.0
117
+ }
118
+ }
119
+
120
+ impl Default for StepId {
121
+ fn default() -> Self {
122
+ Self::new()
123
+ }
124
+ }
125
+
126
+ impl fmt::Display for StepId {
127
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128
+ write!(f, "{}", self.0)
129
+ }
130
+ }
131
+
132
+ impl From<String> for StepId {
133
+ fn from(s: String) -> Self {
134
+ Self(s)
135
+ }
136
+ }
137
+
138
+ impl From<&str> for StepId {
139
+ fn from(s: &str) -> Self {
140
+ Self(s.to_string())
141
+ }
142
+ }
143
+
144
+ /// NodeId - Alias for StepId
145
+ pub type NodeId = StepId;
146
+
147
+ // =============================================================================
148
+ // GraphId - Graph definition identifier (design-time)
149
+ // =============================================================================
150
+
151
+ /// GraphId - Graph definition identifier
152
+ ///
153
+ /// Identifies a static execution blueprint (Nodes + Edges).
154
+ /// Versioned and immutable once published.
155
+ ///
156
+ /// Format: `graph_[27-char KSUID]`
157
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
158
+ pub struct GraphId(String);
159
+
160
+ impl GraphId {
161
+ pub fn new() -> Self {
162
+ Self(format!("graph_{}", new_ksuid()))
163
+ }
164
+
165
+ pub fn from_string(s: impl Into<String>) -> Self {
166
+ Self(s.into())
167
+ }
168
+
169
+ pub fn as_str(&self) -> &str {
170
+ &self.0
171
+ }
172
+ }
173
+
174
+ impl Default for GraphId {
175
+ fn default() -> Self {
176
+ Self::new()
177
+ }
178
+ }
179
+
180
+ impl fmt::Display for GraphId {
181
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182
+ write!(f, "{}", self.0)
183
+ }
184
+ }
185
+
186
+ // =============================================================================
187
+ // ArtifactId - Data produced by a step
188
+ // =============================================================================
189
+
190
+ /// ArtifactId - Identifies persisted output produced by a step
191
+ ///
192
+ /// Examples: Generated Code, PDF Report, Search JSON, Image.
193
+ ///
194
+ /// Format: `artifact_[27-char KSUID]`
195
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
196
+ pub struct ArtifactId(String);
197
+
198
+ impl ArtifactId {
199
+ pub fn new() -> Self {
200
+ Self(format!("artifact_{}", new_ksuid()))
201
+ }
202
+
203
+ pub fn from_string(s: impl Into<String>) -> Self {
204
+ Self(s.into())
205
+ }
206
+
207
+ pub fn as_str(&self) -> &str {
208
+ &self.0
209
+ }
210
+ }
211
+
212
+ impl Default for ArtifactId {
213
+ fn default() -> Self {
214
+ Self::new()
215
+ }
216
+ }
217
+
218
+ impl fmt::Display for ArtifactId {
219
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220
+ write!(f, "{}", self.0)
221
+ }
222
+ }
223
+
224
+ // =============================================================================
225
+ // TenantId / UserId - Multi-tenant context
226
+ // =============================================================================
227
+
228
+ /// TenantId - Multi-tenant isolation identifier
229
+ ///
230
+ /// Format: `tenant_[27-char KSUID]`
231
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
232
+ pub struct TenantId(String);
233
+
234
+ impl TenantId {
235
+ pub fn new() -> Self {
236
+ Self(format!("tenant_{}", new_ksuid()))
237
+ }
238
+
239
+ pub fn from_string(s: impl Into<String>) -> Self {
240
+ Self(s.into())
241
+ }
242
+
243
+ pub fn as_str(&self) -> &str {
244
+ &self.0
245
+ }
246
+ }
247
+
248
+ impl Default for TenantId {
249
+ fn default() -> Self {
250
+ Self::new()
251
+ }
252
+ }
253
+
254
+ impl fmt::Display for TenantId {
255
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256
+ write!(f, "{}", self.0)
257
+ }
258
+ }
259
+
260
+ impl From<String> for TenantId {
261
+ fn from(s: String) -> Self {
262
+ Self(s)
263
+ }
264
+ }
265
+
266
+ impl From<&str> for TenantId {
267
+ fn from(s: &str) -> Self {
268
+ Self(s.to_string())
269
+ }
270
+ }
271
+
272
+ /// UserId - User identifier
273
+ ///
274
+ /// Format: `user_[27-char KSUID]`
275
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
276
+ pub struct UserId(String);
277
+
278
+ impl UserId {
279
+ pub fn new() -> Self {
280
+ Self(format!("user_{}", new_ksuid()))
281
+ }
282
+
283
+ pub fn from_string(s: impl Into<String>) -> Self {
284
+ Self(s.into())
285
+ }
286
+
287
+ pub fn as_str(&self) -> &str {
288
+ &self.0
289
+ }
290
+ }
291
+
292
+ impl Default for UserId {
293
+ fn default() -> Self {
294
+ Self::new()
295
+ }
296
+ }
297
+
298
+ impl fmt::Display for UserId {
299
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300
+ write!(f, "{}", self.0)
301
+ }
302
+ }
303
+
304
+ impl From<String> for UserId {
305
+ fn from(s: String) -> Self {
306
+ Self(s)
307
+ }
308
+ }
309
+
310
+ impl From<&str> for UserId {
311
+ fn from(s: &str) -> Self {
312
+ Self(s.to_string())
313
+ }
314
+ }
315
+
316
+ // =============================================================================
317
+ // StepType - What kind of step this is
318
+ // =============================================================================
319
+
320
+ /// StepType - Defines what the step *is* (internal: NodeType)
321
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322
+ #[serde(rename_all = "PascalCase")]
323
+ pub enum StepType {
324
+ /// LLM call step
325
+ LlmNode,
326
+ /// Sub-agent / sub-graph execution
327
+ GraphNode,
328
+ /// Tool execution
329
+ ToolNode,
330
+ /// Deterministic code function
331
+ FunctionNode,
332
+ /// Routing decision node
333
+ RouterNode,
334
+ /// Conditional branch node
335
+ BranchNode,
336
+ /// Loop iteration node
337
+ LoopNode,
338
+ }
339
+
340
+ impl fmt::Display for StepType {
341
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342
+ match self {
343
+ StepType::LlmNode => write!(f, "LlmNode"),
344
+ StepType::GraphNode => write!(f, "GraphNode"),
345
+ StepType::ToolNode => write!(f, "ToolNode"),
346
+ StepType::FunctionNode => write!(f, "FunctionNode"),
347
+ StepType::RouterNode => write!(f, "RouterNode"),
348
+ StepType::BranchNode => write!(f, "BranchNode"),
349
+ StepType::LoopNode => write!(f, "LoopNode"),
350
+ }
351
+ }
352
+ }
353
+
354
+ // =============================================================================
355
+ // CallableType - What kind of callable was invoked
356
+ // =============================================================================
357
+
358
+ /// CallableType - The type of callable that was invoked
359
+ ///
360
+ /// Used for billing, traceability, and audit trails. Unlike `name` which can change,
361
+ /// `callable_id` + `callable_type` provide stable identifiers for cost attribution.
362
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
363
+ #[serde(rename_all = "snake_case")]
364
+ pub enum CallableType {
365
+ /// Single LLM call, no history
366
+ Completion,
367
+ /// Multi-turn conversation
368
+ Chat,
369
+ /// Goal-driven with tool use
370
+ Agent,
371
+ /// Compiled graph execution
372
+ Workflow,
373
+ /// Ephemeral tasks (title gen, summarization)
374
+ Background,
375
+ /// Callable that invokes other callables
376
+ Composite,
377
+ /// Tool execution (wraps a tool as callable)
378
+ Tool,
379
+ /// Custom/user-defined callable
380
+ Custom,
381
+ }
382
+
383
+ impl fmt::Display for CallableType {
384
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385
+ match self {
386
+ CallableType::Completion => write!(f, "completion"),
387
+ CallableType::Chat => write!(f, "chat"),
388
+ CallableType::Agent => write!(f, "agent"),
389
+ CallableType::Workflow => write!(f, "workflow"),
390
+ CallableType::Background => write!(f, "background"),
391
+ CallableType::Composite => write!(f, "composite"),
392
+ CallableType::Tool => write!(f, "tool"),
393
+ CallableType::Custom => write!(f, "custom"),
394
+ }
395
+ }
396
+ }
397
+
398
+ impl Default for CallableType {
399
+ fn default() -> Self {
400
+ CallableType::Agent
401
+ }
402
+ }
403
+
404
+ // =============================================================================
405
+ // Parent Linkage - Polymorphic root for causal tracing
406
+ // =============================================================================
407
+
408
+ /// ParentType - What kind of entity triggered this execution
409
+ ///
410
+ /// Instead of many nullable root fields, we use a single parent pointer
411
+ /// to trace causal origin.
412
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
413
+ #[serde(rename_all = "snake_case")]
414
+ pub enum ParentType {
415
+ /// User chatted in UI (e.g., msg_1234)
416
+ UserMessage,
417
+ /// Scheduled job (e.g., evt_cron_daily)
418
+ ScheduleEvent,
419
+ /// A parent agent invoked a sub-agent (e.g., step_abc_789)
420
+ StepExecution,
421
+ /// External system trigger (e.g., wh_stripe_hook)
422
+ Webhook,
423
+ /// Agent-to-agent request
424
+ A2aRequest,
425
+ /// System-initiated (startup, recovery)
426
+ System,
427
+ /// Assistant message in a thread
428
+ AssistantMessage,
429
+ /// Start of a thread (no parent)
430
+ ThreadStart,
431
+ /// Result of a tool execution
432
+ ToolResult,
433
+ }
434
+
435
+ impl fmt::Display for ParentType {
436
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437
+ match self {
438
+ ParentType::UserMessage => write!(f, "user_message"),
439
+ ParentType::ScheduleEvent => write!(f, "schedule_event"),
440
+ ParentType::StepExecution => write!(f, "step_execution"),
441
+ ParentType::Webhook => write!(f, "webhook"),
442
+ ParentType::A2aRequest => write!(f, "a2a_request"),
443
+ ParentType::System => write!(f, "system"),
444
+ ParentType::AssistantMessage => write!(f, "assistant_message"),
445
+ ParentType::ThreadStart => write!(f, "thread_start"),
446
+ ParentType::ToolResult => write!(f, "tool_result"),
447
+ }
448
+ }
449
+ }
450
+
451
+ /// ParentLink - Traces causal origin of an execution
452
+ ///
453
+ /// Every execution has a parent link that describes what triggered it.
454
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
455
+ pub struct ParentLink {
456
+ /// The ID of the triggering entity
457
+ pub parent_id: String,
458
+ /// What kind of entity triggered this
459
+ pub parent_type: ParentType,
460
+ }
461
+
462
+ impl ParentLink {
463
+ /// Create a new ParentLink
464
+ pub fn new(parent_id: impl Into<String>, parent_type: ParentType) -> Self {
465
+ Self {
466
+ parent_id: parent_id.into(),
467
+ parent_type,
468
+ }
469
+ }
470
+
471
+ /// Create a ParentLink for a user message trigger
472
+ pub fn from_user_message(message_id: impl Into<String>) -> Self {
473
+ Self::new(message_id, ParentType::UserMessage)
474
+ }
475
+
476
+ /// Create a ParentLink for a step execution (sub-agent) trigger
477
+ pub fn from_step(step_id: &StepId) -> Self {
478
+ Self::new(step_id.as_str(), ParentType::StepExecution)
479
+ }
480
+
481
+ /// Create a ParentLink for a parent execution (nested execution)
482
+ pub fn execution(execution_id: ExecutionId) -> Self {
483
+ Self::new(execution_id.as_str(), ParentType::StepExecution)
484
+ }
485
+
486
+ /// Create a ParentLink for a system trigger
487
+ pub fn system() -> Self {
488
+ Self::new("system", ParentType::System)
489
+ }
490
+ }
491
+
492
+ // =============================================================================
493
+ // StepSource - Tracks why/how a step was created
494
+ // =============================================================================
495
+
496
+ /// StepSourceType - Why/how a step was created
497
+ ///
498
+ /// Used to track the origin of each step for audit trails and replay.
499
+ /// Follows the same pattern as ParentType but at the step level.
500
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
501
+ #[serde(rename_all = "snake_case")]
502
+ pub enum StepSourceType {
503
+ /// Part of initial compiled graph (default)
504
+ InitialPlan,
505
+ /// Discovered during agentic execution
506
+ Discovered,
507
+ /// Retry of a failed step
508
+ Retry,
509
+ /// From user guidance via inbox
510
+ UserGuidance,
511
+ /// From tool result suggesting new work
512
+ ToolResult,
513
+ /// From LLM output suggesting new work
514
+ LlmOutput,
515
+ /// From external agent (A2A)
516
+ A2aMessage,
517
+ }
518
+
519
+ impl fmt::Display for StepSourceType {
520
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521
+ match self {
522
+ StepSourceType::InitialPlan => write!(f, "initial_plan"),
523
+ StepSourceType::Discovered => write!(f, "discovered"),
524
+ StepSourceType::Retry => write!(f, "retry"),
525
+ StepSourceType::UserGuidance => write!(f, "user_guidance"),
526
+ StepSourceType::ToolResult => write!(f, "tool_result"),
527
+ StepSourceType::LlmOutput => write!(f, "llm_output"),
528
+ StepSourceType::A2aMessage => write!(f, "a2a_message"),
529
+ }
530
+ }
531
+ }
532
+
533
+ impl Default for StepSourceType {
534
+ fn default() -> Self {
535
+ StepSourceType::InitialPlan
536
+ }
537
+ }
538
+
539
+ /// StepSource - Tracks the origin of a step
540
+ ///
541
+ /// Every step can optionally track why it was created, enabling:
542
+ /// - Full audit trails for discovered steps
543
+ /// - Replay of agentic discovery sequences
544
+ /// - Understanding step chains and dependencies
545
+ ///
546
+ /// @see packages/enact-schemas/src/execution.schemas.ts - stepSourceSchema
547
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
548
+ #[serde(rename_all = "camelCase")]
549
+ pub struct StepSource {
550
+ /// Type of source (why was this step created)
551
+ pub source_type: StepSourceType,
552
+ /// ID of the step/entity that triggered this step (if any)
553
+ #[serde(skip_serializing_if = "Option::is_none")]
554
+ pub triggered_by: Option<String>,
555
+ /// Human-readable reason for step creation
556
+ #[serde(skip_serializing_if = "Option::is_none")]
557
+ pub reason: Option<String>,
558
+ /// Discovery depth (for discovered steps - how deep in discovery chain)
559
+ #[serde(skip_serializing_if = "Option::is_none")]
560
+ pub depth: Option<u32>,
561
+ /// How this step was spawned (for spawned steps)
562
+ #[serde(skip_serializing_if = "Option::is_none")]
563
+ pub spawn_mode: Option<SpawnMode>,
564
+ }
565
+
566
+ impl StepSource {
567
+ /// Create a new StepSource
568
+ pub fn new(source_type: StepSourceType) -> Self {
569
+ Self {
570
+ source_type,
571
+ triggered_by: None,
572
+ reason: None,
573
+ depth: None,
574
+ spawn_mode: None,
575
+ }
576
+ }
577
+
578
+ /// Create a source for initial plan steps
579
+ pub fn initial_plan() -> Self {
580
+ Self::new(StepSourceType::InitialPlan)
581
+ }
582
+
583
+ /// Create a source for discovered steps
584
+ pub fn discovered(triggered_by: &StepId, reason: impl Into<String>, depth: u32) -> Self {
585
+ Self {
586
+ source_type: StepSourceType::Discovered,
587
+ triggered_by: Some(triggered_by.as_str().to_string()),
588
+ reason: Some(reason.into()),
589
+ depth: Some(depth),
590
+ spawn_mode: None,
591
+ }
592
+ }
593
+
594
+ /// Create a source for retry steps
595
+ pub fn retry(original_step_id: &StepId) -> Self {
596
+ Self {
597
+ source_type: StepSourceType::Retry,
598
+ triggered_by: Some(original_step_id.as_str().to_string()),
599
+ reason: None,
600
+ depth: None,
601
+ spawn_mode: None,
602
+ }
603
+ }
604
+
605
+ /// Create a source for user guidance steps
606
+ pub fn user_guidance(message_id: impl Into<String>, reason: impl Into<String>) -> Self {
607
+ Self {
608
+ source_type: StepSourceType::UserGuidance,
609
+ triggered_by: Some(message_id.into()),
610
+ reason: Some(reason.into()),
611
+ depth: None,
612
+ spawn_mode: None,
613
+ }
614
+ }
615
+
616
+ /// Create a source for LLM output discovery
617
+ pub fn llm_output(step_id: &StepId, reason: impl Into<String>, depth: u32) -> Self {
618
+ Self {
619
+ source_type: StepSourceType::LlmOutput,
620
+ triggered_by: Some(step_id.as_str().to_string()),
621
+ reason: Some(reason.into()),
622
+ depth: Some(depth),
623
+ spawn_mode: None,
624
+ }
625
+ }
626
+
627
+ /// Create a source for tool result discovery
628
+ pub fn tool_result(step_id: &StepId, reason: impl Into<String>, depth: u32) -> Self {
629
+ Self {
630
+ source_type: StepSourceType::ToolResult,
631
+ triggered_by: Some(step_id.as_str().to_string()),
632
+ reason: Some(reason.into()),
633
+ depth: Some(depth),
634
+ spawn_mode: None,
635
+ }
636
+ }
637
+
638
+ /// Set the spawn_mode field
639
+ pub fn with_spawn_mode(mut self, spawn_mode: SpawnMode) -> Self {
640
+ self.spawn_mode = Some(spawn_mode);
641
+ self
642
+ }
643
+
644
+ /// Set the triggered_by field
645
+ pub fn with_triggered_by(mut self, triggered_by: impl Into<String>) -> Self {
646
+ self.triggered_by = Some(triggered_by.into());
647
+ self
648
+ }
649
+
650
+ /// Set the reason field
651
+ pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
652
+ self.reason = Some(reason.into());
653
+ self
654
+ }
655
+
656
+ /// Set the depth field
657
+ pub fn with_depth(mut self, depth: u32) -> Self {
658
+ self.depth = Some(depth);
659
+ self
660
+ }
661
+ }
662
+
663
+ impl Default for StepSource {
664
+ fn default() -> Self {
665
+ Self::initial_plan()
666
+ }
667
+ }
668
+
669
+ // =============================================================================
670
+ // SpawnMode - Execution Isolation Control
671
+ // =============================================================================
672
+
673
+ /// SpawnMode - How a callable spawns child work
674
+ ///
675
+ /// Controls execution isolation, inbox routing, and cancellation behavior.
676
+ /// Every callable can spawn children - SpawnMode determines HOW.
677
+ ///
678
+ /// @see packages/enact-schemas/src/execution.schemas.ts - spawnModeSchema
679
+ /// @see docs/TECHNICAL/32-SPAWN-MODE.md
680
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
681
+ #[serde(tag = "mode", rename_all = "snake_case")]
682
+ pub enum SpawnMode {
683
+ /// Child work joins parent's pending_steps queue (same ExecutionId)
684
+ /// - Same execution context
685
+ /// - Same inbox
686
+ /// - Same policies
687
+ Inline,
688
+
689
+ /// Child work runs as isolated execution (new ExecutionId with ParentLink)
690
+ Child {
691
+ /// Run in background (don't block parent)
692
+ #[serde(default)]
693
+ background: bool,
694
+
695
+ /// Inherit parent's inbox (forward messages)
696
+ #[serde(default)]
697
+ inherit_inbox: bool,
698
+
699
+ /// Override policies for child (JSON object)
700
+ #[serde(skip_serializing_if = "Option::is_none")]
701
+ policies: Option<serde_json::Value>,
702
+ },
703
+ }
704
+
705
+ impl Default for SpawnMode {
706
+ fn default() -> Self {
707
+ SpawnMode::Inline
708
+ }
709
+ }
710
+
711
+ impl SpawnMode {
712
+ /// Create inline spawn mode (default)
713
+ pub fn inline() -> Self {
714
+ SpawnMode::Inline
715
+ }
716
+
717
+ /// Create child spawn mode with options
718
+ pub fn child(background: bool, inherit_inbox: bool) -> Self {
719
+ SpawnMode::Child {
720
+ background,
721
+ inherit_inbox,
722
+ policies: None,
723
+ }
724
+ }
725
+
726
+ /// Create child spawn mode with custom policies
727
+ pub fn child_with_policies(
728
+ background: bool,
729
+ inherit_inbox: bool,
730
+ policies: serde_json::Value,
731
+ ) -> Self {
732
+ SpawnMode::Child {
733
+ background,
734
+ inherit_inbox,
735
+ policies: Some(policies),
736
+ }
737
+ }
738
+
739
+ /// Check if this is inline mode
740
+ pub fn is_inline(&self) -> bool {
741
+ matches!(self, SpawnMode::Inline)
742
+ }
743
+
744
+ /// Check if this is child mode
745
+ pub fn is_child(&self) -> bool {
746
+ matches!(self, SpawnMode::Child { .. })
747
+ }
748
+
749
+ /// Check if child should run in background
750
+ pub fn is_background(&self) -> bool {
751
+ matches!(self, SpawnMode::Child { background: true, .. })
752
+ }
753
+
754
+ /// Check if child should inherit parent's inbox
755
+ pub fn inherits_inbox(&self) -> bool {
756
+ matches!(self, SpawnMode::Child { inherit_inbox: true, .. })
757
+ }
758
+ }
759
+
760
+ impl fmt::Display for SpawnMode {
761
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
762
+ match self {
763
+ SpawnMode::Inline => write!(f, "inline"),
764
+ SpawnMode::Child { background, inherit_inbox, .. } => {
765
+ write!(f, "child(background={}, inherit_inbox={})", background, inherit_inbox)
766
+ }
767
+ }
768
+ }
769
+ }
770
+
771
+ // =============================================================================
772
+ // CancellationPolicy - Child Execution Lifecycle
773
+ // =============================================================================
774
+
775
+ /// CancellationPolicy - What happens to children when parent is cancelled
776
+ ///
777
+ /// Controls the lifecycle relationship between parent and child executions.
778
+ ///
779
+ /// @see packages/enact-schemas/src/execution.schemas.ts - cancellationPolicySchema
780
+ /// @see docs/TECHNICAL/32-SPAWN-MODE.md
781
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
782
+ #[serde(rename_all = "snake_case")]
783
+ pub enum CancellationPolicy {
784
+ /// Cancel all children when parent cancelled (default)
785
+ CascadeCancel,
786
+
787
+ /// Let children complete, parent waits for them
788
+ WaitForChildren,
789
+
790
+ /// Detach children (they continue independently)
791
+ Detach,
792
+ }
793
+
794
+ impl Default for CancellationPolicy {
795
+ fn default() -> Self {
796
+ CancellationPolicy::CascadeCancel
797
+ }
798
+ }
799
+
800
+ impl fmt::Display for CancellationPolicy {
801
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
802
+ match self {
803
+ CancellationPolicy::CascadeCancel => write!(f, "cascade_cancel"),
804
+ CancellationPolicy::WaitForChildren => write!(f, "wait_for_children"),
805
+ CancellationPolicy::Detach => write!(f, "detach"),
806
+ }
807
+ }
808
+ }
809
+
810
+ // =============================================================================
811
+ // ThreadId / MessageId - Conversation hierarchy
812
+ // =============================================================================
813
+
814
+ /// ThreadId - Conversation thread identifier
815
+ ///
816
+ /// Groups messages into a single conversation context.
817
+ ///
818
+ /// Format: `thread_[27-char KSUID]`
819
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
820
+ pub struct ThreadId(String);
821
+
822
+ impl ThreadId {
823
+ pub fn new() -> Self {
824
+ Self(format!("thread_{}", new_ksuid()))
825
+ }
826
+
827
+ pub fn from_string(s: impl Into<String>) -> Self {
828
+ Self(s.into())
829
+ }
830
+
831
+ pub fn as_str(&self) -> &str {
832
+ &self.0
833
+ }
834
+ }
835
+
836
+ impl Default for ThreadId {
837
+ fn default() -> Self {
838
+ Self::new()
839
+ }
840
+ }
841
+
842
+ impl fmt::Display for ThreadId {
843
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844
+ write!(f, "{}", self.0)
845
+ }
846
+ }
847
+
848
+ impl From<String> for ThreadId {
849
+ fn from(s: String) -> Self {
850
+ Self(s)
851
+ }
852
+ }
853
+
854
+ impl From<&str> for ThreadId {
855
+ fn from(s: &str) -> Self {
856
+ Self(s.to_string())
857
+ }
858
+ }
859
+
860
+ /// MessageId - Individual message identifier
861
+ ///
862
+ /// Identifies a single message within a thread.
863
+ ///
864
+ /// Format: `msg_[27-char KSUID]`
865
+ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
866
+ pub struct MessageId(String);
867
+
868
+ impl MessageId {
869
+ pub fn new() -> Self {
870
+ Self(format!("msg_{}", new_ksuid()))
871
+ }
872
+
873
+ pub fn from_string(s: impl Into<String>) -> Self {
874
+ Self(s.into())
875
+ }
876
+
877
+ pub fn as_str(&self) -> &str {
878
+ &self.0
879
+ }
880
+ }
881
+
882
+ impl Default for MessageId {
883
+ fn default() -> Self {
884
+ Self::new()
885
+ }
886
+ }
887
+
888
+ impl fmt::Display for MessageId {
889
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
890
+ write!(f, "{}", self.0)
891
+ }
892
+ }
893
+
894
+ impl From<String> for MessageId {
895
+ fn from(s: String) -> Self {
896
+ Self(s)
897
+ }
898
+ }
899
+
900
+ impl From<&str> for MessageId {
901
+ fn from(s: &str) -> Self {
902
+ Self(s.to_string())
903
+ }
904
+ }
905
+
906
+ // =============================================================================
907
+ // ID Prefix Constants
908
+ // =============================================================================
909
+
910
+ /// ID prefix constants for validation and generation
911
+ pub mod prefixes {
912
+ pub const EXECUTION: &str = "exec_";
913
+ pub const STEP: &str = "step_";
914
+ pub const ARTIFACT: &str = "artifact_";
915
+ pub const GRAPH: &str = "graph_";
916
+ pub const TENANT: &str = "tenant_";
917
+ pub const USER: &str = "user_";
918
+ pub const EVENT: &str = "evt_";
919
+ pub const DECISION: &str = "dec_";
920
+ pub const CONTROL: &str = "ctrl_";
921
+ pub const THREAD: &str = "thread_";
922
+ pub const MESSAGE: &str = "msg_";
923
+ }
924
+
925
+ #[cfg(test)]
926
+ mod tests {
927
+ use super::*;
928
+ use std::collections::HashSet;
929
+
930
+ // =========================================================================
931
+ // ExecutionId Tests
932
+ // =========================================================================
933
+
934
+ #[test]
935
+ fn test_execution_id_new_has_correct_prefix() {
936
+ let id = ExecutionId::new();
937
+ assert!(
938
+ id.as_str().starts_with("exec_"),
939
+ "ExecutionId should start with 'exec_'"
940
+ );
941
+ }
942
+
943
+ #[test]
944
+ fn test_execution_id_new_unique() {
945
+ let id1 = ExecutionId::new();
946
+ let id2 = ExecutionId::new();
947
+ assert_ne!(id1, id2, "Each new ExecutionId should be unique");
948
+ }
949
+
950
+ #[test]
951
+ fn test_execution_id_from_string() {
952
+ let id = ExecutionId::from_string("exec_custom123");
953
+ assert_eq!(id.as_str(), "exec_custom123");
954
+ }
955
+
956
+ #[test]
957
+ fn test_execution_id_from_string_owned() {
958
+ let id = ExecutionId::from_string(String::from("exec_owned"));
959
+ assert_eq!(id.as_str(), "exec_owned");
960
+ }
961
+
962
+ #[test]
963
+ fn test_execution_id_display() {
964
+ let id = ExecutionId::from_string("exec_display_test");
965
+ assert_eq!(format!("{}", id), "exec_display_test");
966
+ }
967
+
968
+ #[test]
969
+ fn test_execution_id_default() {
970
+ let id = ExecutionId::default();
971
+ assert!(id.as_str().starts_with("exec_"));
972
+ }
973
+
974
+ #[test]
975
+ fn test_execution_id_from_string_trait() {
976
+ let id: ExecutionId = String::from("exec_from_string").into();
977
+ assert_eq!(id.as_str(), "exec_from_string");
978
+ }
979
+
980
+ #[test]
981
+ fn test_execution_id_from_str_trait() {
982
+ let id: ExecutionId = "exec_from_str".into();
983
+ assert_eq!(id.as_str(), "exec_from_str");
984
+ }
985
+
986
+ #[test]
987
+ fn test_execution_id_clone() {
988
+ let id1 = ExecutionId::new();
989
+ let id2 = id1.clone();
990
+ assert_eq!(id1, id2);
991
+ }
992
+
993
+ #[test]
994
+ fn test_execution_id_hash() {
995
+ let id1 = ExecutionId::from_string("exec_hash_test");
996
+ let id2 = ExecutionId::from_string("exec_hash_test");
997
+ let mut set = HashSet::new();
998
+ set.insert(id1);
999
+ assert!(set.contains(&id2));
1000
+ }
1001
+
1002
+ #[test]
1003
+ fn test_execution_id_debug() {
1004
+ let id = ExecutionId::from_string("exec_debug");
1005
+ let debug_str = format!("{:?}", id);
1006
+ assert!(debug_str.contains("exec_debug"));
1007
+ }
1008
+
1009
+ #[test]
1010
+ fn test_execution_id_serde() {
1011
+ let id = ExecutionId::from_string("exec_serde_test");
1012
+ let serialized = serde_json::to_string(&id).unwrap();
1013
+ let deserialized: ExecutionId = serde_json::from_str(&serialized).unwrap();
1014
+ assert_eq!(id, deserialized);
1015
+ }
1016
+
1017
+ // =========================================================================
1018
+ // StepId Tests
1019
+ // =========================================================================
1020
+
1021
+ #[test]
1022
+ fn test_step_id_new_has_correct_prefix() {
1023
+ let id = StepId::new();
1024
+ assert!(
1025
+ id.as_str().starts_with("step_"),
1026
+ "StepId should start with 'step_'"
1027
+ );
1028
+ }
1029
+
1030
+ #[test]
1031
+ fn test_step_id_new_unique() {
1032
+ let id1 = StepId::new();
1033
+ let id2 = StepId::new();
1034
+ assert_ne!(id1, id2);
1035
+ }
1036
+
1037
+ #[test]
1038
+ fn test_step_id_from_string() {
1039
+ let id = StepId::from_string("step_custom");
1040
+ assert_eq!(id.as_str(), "step_custom");
1041
+ }
1042
+
1043
+ #[test]
1044
+ fn test_step_id_display() {
1045
+ let id = StepId::from_string("step_display");
1046
+ assert_eq!(format!("{}", id), "step_display");
1047
+ }
1048
+
1049
+ #[test]
1050
+ fn test_step_id_default() {
1051
+ let id = StepId::default();
1052
+ assert!(id.as_str().starts_with("step_"));
1053
+ }
1054
+
1055
+ #[test]
1056
+ fn test_step_id_from_traits() {
1057
+ let id1: StepId = String::from("step_string").into();
1058
+ let id2: StepId = "step_str".into();
1059
+ assert_eq!(id1.as_str(), "step_string");
1060
+ assert_eq!(id2.as_str(), "step_str");
1061
+ }
1062
+
1063
+ #[test]
1064
+ fn test_step_id_hash() {
1065
+ let id = StepId::from_string("step_hash");
1066
+ let mut set = HashSet::new();
1067
+ set.insert(id.clone());
1068
+ assert!(set.contains(&id));
1069
+ }
1070
+
1071
+ #[test]
1072
+ fn test_step_id_serde() {
1073
+ let id = StepId::from_string("step_serde");
1074
+ let json = serde_json::to_string(&id).unwrap();
1075
+ let parsed: StepId = serde_json::from_str(&json).unwrap();
1076
+ assert_eq!(id, parsed);
1077
+ }
1078
+
1079
+ // =========================================================================
1080
+ // GraphId Tests
1081
+ // =========================================================================
1082
+
1083
+ #[test]
1084
+ fn test_graph_id_new_has_correct_prefix() {
1085
+ let id = GraphId::new();
1086
+ assert!(id.as_str().starts_with("graph_"));
1087
+ }
1088
+
1089
+ #[test]
1090
+ fn test_graph_id_new_unique() {
1091
+ let id1 = GraphId::new();
1092
+ let id2 = GraphId::new();
1093
+ assert_ne!(id1, id2);
1094
+ }
1095
+
1096
+ #[test]
1097
+ fn test_graph_id_from_string() {
1098
+ let id = GraphId::from_string("graph_custom");
1099
+ assert_eq!(id.as_str(), "graph_custom");
1100
+ }
1101
+
1102
+ #[test]
1103
+ fn test_graph_id_display() {
1104
+ let id = GraphId::from_string("graph_display");
1105
+ assert_eq!(format!("{}", id), "graph_display");
1106
+ }
1107
+
1108
+ #[test]
1109
+ fn test_graph_id_default() {
1110
+ let id = GraphId::default();
1111
+ assert!(id.as_str().starts_with("graph_"));
1112
+ }
1113
+
1114
+ #[test]
1115
+ fn test_graph_id_serde() {
1116
+ let id = GraphId::from_string("graph_serde");
1117
+ let json = serde_json::to_string(&id).unwrap();
1118
+ let parsed: GraphId = serde_json::from_str(&json).unwrap();
1119
+ assert_eq!(id, parsed);
1120
+ }
1121
+
1122
+ // =========================================================================
1123
+ // ArtifactId Tests
1124
+ // =========================================================================
1125
+
1126
+ #[test]
1127
+ fn test_artifact_id_new_has_correct_prefix() {
1128
+ let id = ArtifactId::new();
1129
+ assert!(id.as_str().starts_with("artifact_"));
1130
+ }
1131
+
1132
+ #[test]
1133
+ fn test_artifact_id_new_unique() {
1134
+ let id1 = ArtifactId::new();
1135
+ let id2 = ArtifactId::new();
1136
+ assert_ne!(id1, id2);
1137
+ }
1138
+
1139
+ #[test]
1140
+ fn test_artifact_id_from_string() {
1141
+ let id = ArtifactId::from_string("artifact_custom");
1142
+ assert_eq!(id.as_str(), "artifact_custom");
1143
+ }
1144
+
1145
+ #[test]
1146
+ fn test_artifact_id_display() {
1147
+ let id = ArtifactId::from_string("artifact_display");
1148
+ assert_eq!(format!("{}", id), "artifact_display");
1149
+ }
1150
+
1151
+ #[test]
1152
+ fn test_artifact_id_default() {
1153
+ let id = ArtifactId::default();
1154
+ assert!(id.as_str().starts_with("artifact_"));
1155
+ }
1156
+
1157
+ #[test]
1158
+ fn test_artifact_id_serde() {
1159
+ let id = ArtifactId::from_string("artifact_serde");
1160
+ let json = serde_json::to_string(&id).unwrap();
1161
+ let parsed: ArtifactId = serde_json::from_str(&json).unwrap();
1162
+ assert_eq!(id, parsed);
1163
+ }
1164
+
1165
+ // =========================================================================
1166
+ // TenantId Tests
1167
+ // =========================================================================
1168
+
1169
+ #[test]
1170
+ fn test_tenant_id_new_has_correct_prefix() {
1171
+ let id = TenantId::new();
1172
+ assert!(id.as_str().starts_with("tenant_"));
1173
+ }
1174
+
1175
+ #[test]
1176
+ fn test_tenant_id_new_unique() {
1177
+ let id1 = TenantId::new();
1178
+ let id2 = TenantId::new();
1179
+ assert_ne!(id1, id2);
1180
+ }
1181
+
1182
+ #[test]
1183
+ fn test_tenant_id_from_string() {
1184
+ let id = TenantId::from_string("tenant_custom");
1185
+ assert_eq!(id.as_str(), "tenant_custom");
1186
+ }
1187
+
1188
+ #[test]
1189
+ fn test_tenant_id_display() {
1190
+ let id = TenantId::from_string("tenant_display");
1191
+ assert_eq!(format!("{}", id), "tenant_display");
1192
+ }
1193
+
1194
+ #[test]
1195
+ fn test_tenant_id_default() {
1196
+ let id = TenantId::default();
1197
+ assert!(id.as_str().starts_with("tenant_"));
1198
+ }
1199
+
1200
+ #[test]
1201
+ fn test_tenant_id_from_traits() {
1202
+ let id1: TenantId = String::from("tenant_string").into();
1203
+ let id2: TenantId = "tenant_str".into();
1204
+ assert_eq!(id1.as_str(), "tenant_string");
1205
+ assert_eq!(id2.as_str(), "tenant_str");
1206
+ }
1207
+
1208
+ #[test]
1209
+ fn test_tenant_id_serde() {
1210
+ let id = TenantId::from_string("tenant_serde");
1211
+ let json = serde_json::to_string(&id).unwrap();
1212
+ let parsed: TenantId = serde_json::from_str(&json).unwrap();
1213
+ assert_eq!(id, parsed);
1214
+ }
1215
+
1216
+ // =========================================================================
1217
+ // UserId Tests
1218
+ // =========================================================================
1219
+
1220
+ #[test]
1221
+ fn test_user_id_new_has_correct_prefix() {
1222
+ let id = UserId::new();
1223
+ assert!(id.as_str().starts_with("user_"));
1224
+ }
1225
+
1226
+ #[test]
1227
+ fn test_user_id_new_unique() {
1228
+ let id1 = UserId::new();
1229
+ let id2 = UserId::new();
1230
+ assert_ne!(id1, id2);
1231
+ }
1232
+
1233
+ #[test]
1234
+ fn test_user_id_from_string() {
1235
+ let id = UserId::from_string("user_custom");
1236
+ assert_eq!(id.as_str(), "user_custom");
1237
+ }
1238
+
1239
+ #[test]
1240
+ fn test_user_id_display() {
1241
+ let id = UserId::from_string("user_display");
1242
+ assert_eq!(format!("{}", id), "user_display");
1243
+ }
1244
+
1245
+ #[test]
1246
+ fn test_user_id_default() {
1247
+ let id = UserId::default();
1248
+ assert!(id.as_str().starts_with("user_"));
1249
+ }
1250
+
1251
+ #[test]
1252
+ fn test_user_id_from_traits() {
1253
+ let id1: UserId = String::from("user_string").into();
1254
+ let id2: UserId = "user_str".into();
1255
+ assert_eq!(id1.as_str(), "user_string");
1256
+ assert_eq!(id2.as_str(), "user_str");
1257
+ }
1258
+
1259
+ #[test]
1260
+ fn test_user_id_serde() {
1261
+ let id = UserId::from_string("user_serde");
1262
+ let json = serde_json::to_string(&id).unwrap();
1263
+ let parsed: UserId = serde_json::from_str(&json).unwrap();
1264
+ assert_eq!(id, parsed);
1265
+ }
1266
+
1267
+ // =========================================================================
1268
+ // StepType Tests
1269
+ // =========================================================================
1270
+
1271
+ #[test]
1272
+ fn test_step_type_display_llm_node() {
1273
+ assert_eq!(format!("{}", StepType::LlmNode), "LlmNode");
1274
+ }
1275
+
1276
+ #[test]
1277
+ fn test_step_type_display_graph_node() {
1278
+ assert_eq!(format!("{}", StepType::GraphNode), "GraphNode");
1279
+ }
1280
+
1281
+ #[test]
1282
+ fn test_step_type_display_tool_node() {
1283
+ assert_eq!(format!("{}", StepType::ToolNode), "ToolNode");
1284
+ }
1285
+
1286
+ #[test]
1287
+ fn test_step_type_display_function_node() {
1288
+ assert_eq!(format!("{}", StepType::FunctionNode), "FunctionNode");
1289
+ }
1290
+
1291
+ #[test]
1292
+ fn test_step_type_display_router_node() {
1293
+ assert_eq!(format!("{}", StepType::RouterNode), "RouterNode");
1294
+ }
1295
+
1296
+ #[test]
1297
+ fn test_step_type_display_branch_node() {
1298
+ assert_eq!(format!("{}", StepType::BranchNode), "BranchNode");
1299
+ }
1300
+
1301
+ #[test]
1302
+ fn test_step_type_display_loop_node() {
1303
+ assert_eq!(format!("{}", StepType::LoopNode), "LoopNode");
1304
+ }
1305
+
1306
+ #[test]
1307
+ fn test_step_type_serde_llm_node() {
1308
+ let step = StepType::LlmNode;
1309
+ let json = serde_json::to_string(&step).unwrap();
1310
+ assert_eq!(json, "\"LlmNode\"");
1311
+ let parsed: StepType = serde_json::from_str(&json).unwrap();
1312
+ assert_eq!(parsed, StepType::LlmNode);
1313
+ }
1314
+
1315
+ #[test]
1316
+ fn test_step_type_serde_all_variants() {
1317
+ let variants = vec![
1318
+ StepType::LlmNode,
1319
+ StepType::GraphNode,
1320
+ StepType::ToolNode,
1321
+ StepType::FunctionNode,
1322
+ StepType::RouterNode,
1323
+ StepType::BranchNode,
1324
+ StepType::LoopNode,
1325
+ ];
1326
+ for variant in variants {
1327
+ let json = serde_json::to_string(&variant).unwrap();
1328
+ let parsed: StepType = serde_json::from_str(&json).unwrap();
1329
+ assert_eq!(parsed, variant);
1330
+ }
1331
+ }
1332
+
1333
+ #[test]
1334
+ fn test_step_type_equality() {
1335
+ assert_eq!(StepType::LlmNode, StepType::LlmNode);
1336
+ assert_ne!(StepType::LlmNode, StepType::ToolNode);
1337
+ }
1338
+
1339
+ #[test]
1340
+ fn test_step_type_clone() {
1341
+ let step = StepType::GraphNode;
1342
+ let cloned = step.clone();
1343
+ assert_eq!(step, cloned);
1344
+ }
1345
+
1346
+ // =========================================================================
1347
+ // CallableType Tests (for billing/traceability)
1348
+ // =========================================================================
1349
+
1350
+ #[test]
1351
+ fn test_callable_type_display_all() {
1352
+ assert_eq!(format!("{}", CallableType::Completion), "completion");
1353
+ assert_eq!(format!("{}", CallableType::Chat), "chat");
1354
+ assert_eq!(format!("{}", CallableType::Agent), "agent");
1355
+ assert_eq!(format!("{}", CallableType::Workflow), "workflow");
1356
+ assert_eq!(format!("{}", CallableType::Background), "background");
1357
+ assert_eq!(format!("{}", CallableType::Composite), "composite");
1358
+ assert_eq!(format!("{}", CallableType::Tool), "tool");
1359
+ assert_eq!(format!("{}", CallableType::Custom), "custom");
1360
+ }
1361
+
1362
+ #[test]
1363
+ fn test_callable_type_default() {
1364
+ let default_type = CallableType::default();
1365
+ assert_eq!(default_type, CallableType::Agent);
1366
+ }
1367
+
1368
+ #[test]
1369
+ fn test_callable_type_serde_all_variants() {
1370
+ let variants = vec![
1371
+ CallableType::Completion,
1372
+ CallableType::Chat,
1373
+ CallableType::Agent,
1374
+ CallableType::Workflow,
1375
+ CallableType::Background,
1376
+ CallableType::Composite,
1377
+ CallableType::Tool,
1378
+ CallableType::Custom,
1379
+ ];
1380
+ for variant in variants {
1381
+ let json = serde_json::to_string(&variant).unwrap();
1382
+ let parsed: CallableType = serde_json::from_str(&json).unwrap();
1383
+ assert_eq!(parsed, variant);
1384
+ }
1385
+ }
1386
+
1387
+ #[test]
1388
+ fn test_callable_type_serde_snake_case() {
1389
+ // Verify snake_case serialization
1390
+ let json = serde_json::to_string(&CallableType::Background).unwrap();
1391
+ assert_eq!(json, "\"background\"");
1392
+
1393
+ let json = serde_json::to_string(&CallableType::Workflow).unwrap();
1394
+ assert_eq!(json, "\"workflow\"");
1395
+ }
1396
+
1397
+ #[test]
1398
+ fn test_callable_type_equality() {
1399
+ assert_eq!(CallableType::Agent, CallableType::Agent);
1400
+ assert_ne!(CallableType::Agent, CallableType::Chat);
1401
+ }
1402
+
1403
+ #[test]
1404
+ fn test_callable_type_clone() {
1405
+ let callable = CallableType::Workflow;
1406
+ let cloned = callable.clone();
1407
+ assert_eq!(callable, cloned);
1408
+ }
1409
+
1410
+ #[test]
1411
+ fn test_callable_type_hash() {
1412
+ use std::collections::HashSet;
1413
+ let mut set = HashSet::new();
1414
+ set.insert(CallableType::Agent);
1415
+ set.insert(CallableType::Chat);
1416
+ set.insert(CallableType::Agent); // duplicate
1417
+ assert_eq!(set.len(), 2);
1418
+ }
1419
+
1420
+ // =========================================================================
1421
+ // ParentType Tests
1422
+ // =========================================================================
1423
+
1424
+ #[test]
1425
+ fn test_parent_type_display_user_message() {
1426
+ assert_eq!(format!("{}", ParentType::UserMessage), "user_message");
1427
+ }
1428
+
1429
+ #[test]
1430
+ fn test_parent_type_display_schedule_event() {
1431
+ assert_eq!(format!("{}", ParentType::ScheduleEvent), "schedule_event");
1432
+ }
1433
+
1434
+ #[test]
1435
+ fn test_parent_type_display_step_execution() {
1436
+ assert_eq!(format!("{}", ParentType::StepExecution), "step_execution");
1437
+ }
1438
+
1439
+ #[test]
1440
+ fn test_parent_type_display_webhook() {
1441
+ assert_eq!(format!("{}", ParentType::Webhook), "webhook");
1442
+ }
1443
+
1444
+ #[test]
1445
+ fn test_parent_type_display_a2a_request() {
1446
+ assert_eq!(format!("{}", ParentType::A2aRequest), "a2a_request");
1447
+ }
1448
+
1449
+ #[test]
1450
+ fn test_parent_type_display_system() {
1451
+ assert_eq!(format!("{}", ParentType::System), "system");
1452
+ }
1453
+
1454
+ #[test]
1455
+ fn test_parent_type_serde_all_variants() {
1456
+ let variants = vec![
1457
+ ParentType::UserMessage,
1458
+ ParentType::ScheduleEvent,
1459
+ ParentType::StepExecution,
1460
+ ParentType::Webhook,
1461
+ ParentType::A2aRequest,
1462
+ ParentType::System,
1463
+ ];
1464
+ for variant in variants {
1465
+ let json = serde_json::to_string(&variant).unwrap();
1466
+ let parsed: ParentType = serde_json::from_str(&json).unwrap();
1467
+ assert_eq!(parsed, variant);
1468
+ }
1469
+ }
1470
+
1471
+ #[test]
1472
+ fn test_parent_type_equality() {
1473
+ assert_eq!(ParentType::System, ParentType::System);
1474
+ assert_ne!(ParentType::System, ParentType::Webhook);
1475
+ }
1476
+
1477
+ // =========================================================================
1478
+ // ParentLink Tests
1479
+ // =========================================================================
1480
+
1481
+ #[test]
1482
+ fn test_parent_link_new() {
1483
+ let link = ParentLink::new("msg_123", ParentType::UserMessage);
1484
+ assert_eq!(link.parent_id, "msg_123");
1485
+ assert_eq!(link.parent_type, ParentType::UserMessage);
1486
+ }
1487
+
1488
+ #[test]
1489
+ fn test_parent_link_new_with_string() {
1490
+ let link = ParentLink::new(String::from("wh_456"), ParentType::Webhook);
1491
+ assert_eq!(link.parent_id, "wh_456");
1492
+ assert_eq!(link.parent_type, ParentType::Webhook);
1493
+ }
1494
+
1495
+ #[test]
1496
+ fn test_parent_link_from_user_message() {
1497
+ let link = ParentLink::from_user_message("msg_user_789");
1498
+ assert_eq!(link.parent_id, "msg_user_789");
1499
+ assert_eq!(link.parent_type, ParentType::UserMessage);
1500
+ }
1501
+
1502
+ #[test]
1503
+ fn test_parent_link_from_step() {
1504
+ let step_id = StepId::from_string("step_abc123");
1505
+ let link = ParentLink::from_step(&step_id);
1506
+ assert_eq!(link.parent_id, "step_abc123");
1507
+ assert_eq!(link.parent_type, ParentType::StepExecution);
1508
+ }
1509
+
1510
+ #[test]
1511
+ fn test_parent_link_execution() {
1512
+ let exec_id = ExecutionId::from_string("exec_parent");
1513
+ let link = ParentLink::execution(exec_id);
1514
+ assert_eq!(link.parent_id, "exec_parent");
1515
+ assert_eq!(link.parent_type, ParentType::StepExecution);
1516
+ }
1517
+
1518
+ #[test]
1519
+ fn test_parent_link_system() {
1520
+ let link = ParentLink::system();
1521
+ assert_eq!(link.parent_id, "system");
1522
+ assert_eq!(link.parent_type, ParentType::System);
1523
+ }
1524
+
1525
+ #[test]
1526
+ fn test_parent_link_serde() {
1527
+ let link = ParentLink::new("test_id", ParentType::A2aRequest);
1528
+ let json = serde_json::to_string(&link).unwrap();
1529
+ let parsed: ParentLink = serde_json::from_str(&json).unwrap();
1530
+ assert_eq!(link, parsed);
1531
+ }
1532
+
1533
+ #[test]
1534
+ fn test_parent_link_equality() {
1535
+ let link1 = ParentLink::new("same_id", ParentType::System);
1536
+ let link2 = ParentLink::new("same_id", ParentType::System);
1537
+ let link3 = ParentLink::new("different_id", ParentType::System);
1538
+ assert_eq!(link1, link2);
1539
+ assert_ne!(link1, link3);
1540
+ }
1541
+
1542
+ #[test]
1543
+ fn test_parent_link_clone() {
1544
+ let link = ParentLink::from_user_message("msg_clone");
1545
+ let cloned = link.clone();
1546
+ assert_eq!(link, cloned);
1547
+ }
1548
+
1549
+ // =========================================================================
1550
+ // Prefixes Tests
1551
+ // =========================================================================
1552
+
1553
+ #[test]
1554
+ fn test_prefix_execution() {
1555
+ assert_eq!(prefixes::EXECUTION, "exec_");
1556
+ }
1557
+
1558
+ #[test]
1559
+ fn test_prefix_step() {
1560
+ assert_eq!(prefixes::STEP, "step_");
1561
+ }
1562
+
1563
+ #[test]
1564
+ fn test_prefix_artifact() {
1565
+ assert_eq!(prefixes::ARTIFACT, "artifact_");
1566
+ }
1567
+
1568
+ #[test]
1569
+ fn test_prefix_graph() {
1570
+ assert_eq!(prefixes::GRAPH, "graph_");
1571
+ }
1572
+
1573
+ #[test]
1574
+ fn test_prefix_tenant() {
1575
+ assert_eq!(prefixes::TENANT, "tenant_");
1576
+ }
1577
+
1578
+ #[test]
1579
+ fn test_prefix_user() {
1580
+ assert_eq!(prefixes::USER, "user_");
1581
+ }
1582
+
1583
+ #[test]
1584
+ fn test_prefix_event() {
1585
+ assert_eq!(prefixes::EVENT, "evt_");
1586
+ }
1587
+
1588
+ #[test]
1589
+ fn test_prefix_decision() {
1590
+ assert_eq!(prefixes::DECISION, "dec_");
1591
+ }
1592
+
1593
+ #[test]
1594
+ fn test_prefix_control() {
1595
+ assert_eq!(prefixes::CONTROL, "ctrl_");
1596
+ }
1597
+
1598
+ // =========================================================================
1599
+ // Type Alias Tests
1600
+ // =========================================================================
1601
+
1602
+ #[test]
1603
+ fn test_run_id_alias() {
1604
+ let exec_id = ExecutionId::from_string("exec_alias");
1605
+ let run_id: RunId = exec_id.clone();
1606
+ assert_eq!(exec_id.as_str(), run_id.as_str());
1607
+ }
1608
+
1609
+ #[test]
1610
+ fn test_node_id_alias() {
1611
+ let step_id = StepId::from_string("step_alias");
1612
+ let node_id: NodeId = step_id.clone();
1613
+ assert_eq!(step_id.as_str(), node_id.as_str());
1614
+ }
1615
+
1616
+ // =========================================================================
1617
+ // KSUID Format Tests
1618
+ // =========================================================================
1619
+
1620
+ #[test]
1621
+ fn test_execution_id_ksuid_length() {
1622
+ let id = ExecutionId::new();
1623
+ // exec_ (5 chars) + KSUID (27 chars) = 32 chars
1624
+ assert_eq!(id.as_str().len(), 32);
1625
+ }
1626
+
1627
+ #[test]
1628
+ fn test_step_id_ksuid_length() {
1629
+ let id = StepId::new();
1630
+ // step_ (5 chars) + KSUID (27 chars) = 32 chars
1631
+ assert_eq!(id.as_str().len(), 32);
1632
+ }
1633
+
1634
+ #[test]
1635
+ fn test_graph_id_ksuid_length() {
1636
+ let id = GraphId::new();
1637
+ // graph_ (6 chars) + KSUID (27 chars) = 33 chars
1638
+ assert_eq!(id.as_str().len(), 33);
1639
+ }
1640
+
1641
+ #[test]
1642
+ fn test_artifact_id_ksuid_length() {
1643
+ let id = ArtifactId::new();
1644
+ // artifact_ (9 chars) + KSUID (27 chars) = 36 chars
1645
+ assert_eq!(id.as_str().len(), 36);
1646
+ }
1647
+
1648
+ #[test]
1649
+ fn test_tenant_id_ksuid_length() {
1650
+ let id = TenantId::new();
1651
+ // tenant_ (7 chars) + KSUID (27 chars) = 34 chars
1652
+ assert_eq!(id.as_str().len(), 34);
1653
+ }
1654
+
1655
+ #[test]
1656
+ fn test_user_id_ksuid_length() {
1657
+ let id = UserId::new();
1658
+ // user_ (5 chars) + KSUID (27 chars) = 32 chars
1659
+ assert_eq!(id.as_str().len(), 32);
1660
+ }
1661
+
1662
+ // =========================================================================
1663
+ // Sortability Tests (KSUIDs are time-sorted)
1664
+ // =========================================================================
1665
+
1666
+ #[test]
1667
+ fn test_execution_ids_sortable() {
1668
+ // Generate multiple IDs quickly and verify they're all unique
1669
+ let mut ids: Vec<ExecutionId> = (0..10).map(|_| ExecutionId::new()).collect();
1670
+ let original_count = ids.len();
1671
+
1672
+ // Deduplicate to check uniqueness
1673
+ ids.sort_by(|a, b| a.as_str().cmp(b.as_str()));
1674
+ ids.dedup_by(|a, b| a.as_str() == b.as_str());
1675
+ assert_eq!(
1676
+ ids.len(),
1677
+ original_count,
1678
+ "All generated IDs should be unique"
1679
+ );
1680
+ }
1681
+
1682
+ #[test]
1683
+ fn test_ksuid_contains_alphanumeric() {
1684
+ let id = ExecutionId::new();
1685
+ let ksuid_part = &id.as_str()[5..]; // After "exec_"
1686
+ assert!(ksuid_part.chars().all(|c| c.is_ascii_alphanumeric()));
1687
+ }
1688
+
1689
+ // =========================================================================
1690
+ // ThreadId Tests
1691
+ // =========================================================================
1692
+
1693
+ #[test]
1694
+ fn test_thread_id_new_has_correct_prefix() {
1695
+ let id = ThreadId::new();
1696
+ assert!(
1697
+ id.as_str().starts_with("thread_"),
1698
+ "ThreadId should start with 'thread_'"
1699
+ );
1700
+ }
1701
+
1702
+ #[test]
1703
+ fn test_thread_id_new_unique() {
1704
+ let id1 = ThreadId::new();
1705
+ let id2 = ThreadId::new();
1706
+ assert_ne!(id1, id2, "Each new ThreadId should be unique");
1707
+ }
1708
+
1709
+ #[test]
1710
+ fn test_thread_id_from_string() {
1711
+ let id = ThreadId::from_string("thread_custom123");
1712
+ assert_eq!(id.as_str(), "thread_custom123");
1713
+ }
1714
+
1715
+ #[test]
1716
+ fn test_thread_id_display() {
1717
+ let id = ThreadId::from_string("thread_display_test");
1718
+ assert_eq!(format!("{}", id), "thread_display_test");
1719
+ }
1720
+
1721
+ #[test]
1722
+ fn test_thread_id_default() {
1723
+ let id = ThreadId::default();
1724
+ assert!(id.as_str().starts_with("thread_"));
1725
+ }
1726
+
1727
+ #[test]
1728
+ fn test_thread_id_from_traits() {
1729
+ let id1: ThreadId = String::from("thread_string").into();
1730
+ let id2: ThreadId = "thread_str".into();
1731
+ assert_eq!(id1.as_str(), "thread_string");
1732
+ assert_eq!(id2.as_str(), "thread_str");
1733
+ }
1734
+
1735
+ #[test]
1736
+ fn test_thread_id_hash() {
1737
+ let id = ThreadId::from_string("thread_hash");
1738
+ let mut set = HashSet::new();
1739
+ set.insert(id.clone());
1740
+ assert!(set.contains(&id));
1741
+ }
1742
+
1743
+ #[test]
1744
+ fn test_thread_id_serde() {
1745
+ let id = ThreadId::from_string("thread_serde");
1746
+ let json = serde_json::to_string(&id).unwrap();
1747
+ let parsed: ThreadId = serde_json::from_str(&json).unwrap();
1748
+ assert_eq!(id, parsed);
1749
+ }
1750
+
1751
+ #[test]
1752
+ fn test_thread_id_ksuid_length() {
1753
+ let id = ThreadId::new();
1754
+ // thread_ (7 chars) + KSUID (27 chars) = 34 chars
1755
+ assert_eq!(id.as_str().len(), 34);
1756
+ }
1757
+
1758
+ // =========================================================================
1759
+ // MessageId Tests
1760
+ // =========================================================================
1761
+
1762
+ #[test]
1763
+ fn test_message_id_new_has_correct_prefix() {
1764
+ let id = MessageId::new();
1765
+ assert!(
1766
+ id.as_str().starts_with("msg_"),
1767
+ "MessageId should start with 'msg_'"
1768
+ );
1769
+ }
1770
+
1771
+ #[test]
1772
+ fn test_message_id_new_unique() {
1773
+ let id1 = MessageId::new();
1774
+ let id2 = MessageId::new();
1775
+ assert_ne!(id1, id2, "Each new MessageId should be unique");
1776
+ }
1777
+
1778
+ #[test]
1779
+ fn test_message_id_from_string() {
1780
+ let id = MessageId::from_string("msg_custom123");
1781
+ assert_eq!(id.as_str(), "msg_custom123");
1782
+ }
1783
+
1784
+ #[test]
1785
+ fn test_message_id_display() {
1786
+ let id = MessageId::from_string("msg_display_test");
1787
+ assert_eq!(format!("{}", id), "msg_display_test");
1788
+ }
1789
+
1790
+ #[test]
1791
+ fn test_message_id_default() {
1792
+ let id = MessageId::default();
1793
+ assert!(id.as_str().starts_with("msg_"));
1794
+ }
1795
+
1796
+ #[test]
1797
+ fn test_message_id_from_traits() {
1798
+ let id1: MessageId = String::from("msg_string").into();
1799
+ let id2: MessageId = "msg_str".into();
1800
+ assert_eq!(id1.as_str(), "msg_string");
1801
+ assert_eq!(id2.as_str(), "msg_str");
1802
+ }
1803
+
1804
+ #[test]
1805
+ fn test_message_id_hash() {
1806
+ let id = MessageId::from_string("msg_hash");
1807
+ let mut set = HashSet::new();
1808
+ set.insert(id.clone());
1809
+ assert!(set.contains(&id));
1810
+ }
1811
+
1812
+ #[test]
1813
+ fn test_message_id_serde() {
1814
+ let id = MessageId::from_string("msg_serde");
1815
+ let json = serde_json::to_string(&id).unwrap();
1816
+ let parsed: MessageId = serde_json::from_str(&json).unwrap();
1817
+ assert_eq!(id, parsed);
1818
+ }
1819
+
1820
+ #[test]
1821
+ fn test_message_id_ksuid_length() {
1822
+ let id = MessageId::new();
1823
+ // msg_ (4 chars) + KSUID (27 chars) = 31 chars
1824
+ assert_eq!(id.as_str().len(), 31);
1825
+ }
1826
+
1827
+ // =========================================================================
1828
+ // Prefix Tests for new IDs
1829
+ // =========================================================================
1830
+
1831
+ #[test]
1832
+ fn test_prefix_thread() {
1833
+ assert_eq!(prefixes::THREAD, "thread_");
1834
+ }
1835
+
1836
+ #[test]
1837
+ fn test_prefix_message() {
1838
+ assert_eq!(prefixes::MESSAGE, "msg_");
1839
+ }
1840
+
1841
+ // =========================================================================
1842
+ // SpawnMode Tests
1843
+ // =========================================================================
1844
+
1845
+ #[test]
1846
+ fn test_spawn_mode_inline_default() {
1847
+ // Inline is not the default since we use Option<SpawnMode>
1848
+ // but we can still test the variant
1849
+ let mode = SpawnMode::Inline;
1850
+ assert_eq!(mode, SpawnMode::Inline);
1851
+ }
1852
+
1853
+ #[test]
1854
+ fn test_spawn_mode_child_default_fields() {
1855
+ let mode = SpawnMode::Child {
1856
+ background: false,
1857
+ inherit_inbox: false,
1858
+ policies: None,
1859
+ };
1860
+ if let SpawnMode::Child { background, inherit_inbox, policies } = mode {
1861
+ assert!(!background, "default background should be false");
1862
+ assert!(!inherit_inbox, "default inherit_inbox should be false");
1863
+ assert!(policies.is_none(), "default policies should be None");
1864
+ } else {
1865
+ panic!("Expected SpawnMode::Child");
1866
+ }
1867
+ }
1868
+
1869
+ #[test]
1870
+ fn test_spawn_mode_child_with_inherit_inbox() {
1871
+ let mode = SpawnMode::Child {
1872
+ background: false,
1873
+ inherit_inbox: true,
1874
+ policies: None,
1875
+ };
1876
+ if let SpawnMode::Child { inherit_inbox, .. } = mode {
1877
+ assert!(inherit_inbox, "inherit_inbox should be true");
1878
+ } else {
1879
+ panic!("Expected SpawnMode::Child");
1880
+ }
1881
+ }
1882
+
1883
+ #[test]
1884
+ fn test_spawn_mode_child_background() {
1885
+ let mode = SpawnMode::Child {
1886
+ background: true,
1887
+ inherit_inbox: false,
1888
+ policies: None,
1889
+ };
1890
+ if let SpawnMode::Child { background, .. } = mode {
1891
+ assert!(background, "background should be true");
1892
+ } else {
1893
+ panic!("Expected SpawnMode::Child");
1894
+ }
1895
+ }
1896
+
1897
+ #[test]
1898
+ fn test_spawn_mode_serde_inline() {
1899
+ let mode = SpawnMode::Inline;
1900
+ let json = serde_json::to_string(&mode).unwrap();
1901
+ assert!(json.contains("\"mode\":\"inline\""), "Inline should serialize with mode tag");
1902
+ let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
1903
+ assert_eq!(mode, parsed);
1904
+ }
1905
+
1906
+ #[test]
1907
+ fn test_spawn_mode_serde_child() {
1908
+ let mode = SpawnMode::Child {
1909
+ background: true,
1910
+ inherit_inbox: true,
1911
+ policies: None,
1912
+ };
1913
+ let json = serde_json::to_string(&mode).unwrap();
1914
+ assert!(json.contains("\"mode\":\"child\""), "Child should serialize with mode tag");
1915
+ assert!(json.contains("\"background\":true"), "background should be serialized");
1916
+ assert!(json.contains("\"inherit_inbox\":true"), "inherit_inbox should be serialized");
1917
+ let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
1918
+ assert_eq!(mode, parsed);
1919
+ }
1920
+
1921
+ #[test]
1922
+ fn test_spawn_mode_serde_child_with_policies() {
1923
+ let policies = serde_json::json!({
1924
+ "max_steps": 100,
1925
+ "max_tokens": 10000
1926
+ });
1927
+ let mode = SpawnMode::Child {
1928
+ background: false,
1929
+ inherit_inbox: true,
1930
+ policies: Some(policies.clone()),
1931
+ };
1932
+ let json = serde_json::to_string(&mode).unwrap();
1933
+ let parsed: SpawnMode = serde_json::from_str(&json).unwrap();
1934
+ assert_eq!(mode, parsed);
1935
+ if let SpawnMode::Child { policies: Some(p), .. } = parsed {
1936
+ assert_eq!(p["max_steps"], 100);
1937
+ } else {
1938
+ panic!("Expected SpawnMode::Child with policies");
1939
+ }
1940
+ }
1941
+
1942
+ #[test]
1943
+ fn test_spawn_mode_equality() {
1944
+ let mode1 = SpawnMode::Inline;
1945
+ let mode2 = SpawnMode::Inline;
1946
+ let mode3 = SpawnMode::Child {
1947
+ background: false,
1948
+ inherit_inbox: false,
1949
+ policies: None,
1950
+ };
1951
+ assert_eq!(mode1, mode2);
1952
+ assert_ne!(mode1, mode3);
1953
+ }
1954
+
1955
+ #[test]
1956
+ fn test_spawn_mode_clone() {
1957
+ let mode = SpawnMode::Child {
1958
+ background: true,
1959
+ inherit_inbox: true,
1960
+ policies: Some(serde_json::json!({"key": "value"})),
1961
+ };
1962
+ let cloned = mode.clone();
1963
+ assert_eq!(mode, cloned);
1964
+ }
1965
+
1966
+ // =========================================================================
1967
+ // CancellationPolicy Tests
1968
+ // =========================================================================
1969
+
1970
+ #[test]
1971
+ fn test_cancellation_policy_default() {
1972
+ let policy = CancellationPolicy::default();
1973
+ assert_eq!(policy, CancellationPolicy::CascadeCancel);
1974
+ }
1975
+
1976
+ #[test]
1977
+ fn test_cancellation_policy_variants() {
1978
+ let cascade = CancellationPolicy::CascadeCancel;
1979
+ let wait = CancellationPolicy::WaitForChildren;
1980
+ let detach = CancellationPolicy::Detach;
1981
+
1982
+ assert_ne!(cascade, wait);
1983
+ assert_ne!(wait, detach);
1984
+ assert_ne!(cascade, detach);
1985
+ }
1986
+
1987
+ #[test]
1988
+ fn test_cancellation_policy_serde_cascade() {
1989
+ let policy = CancellationPolicy::CascadeCancel;
1990
+ let json = serde_json::to_string(&policy).unwrap();
1991
+ assert_eq!(json, "\"cascade_cancel\"");
1992
+ let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
1993
+ assert_eq!(policy, parsed);
1994
+ }
1995
+
1996
+ #[test]
1997
+ fn test_cancellation_policy_serde_wait() {
1998
+ let policy = CancellationPolicy::WaitForChildren;
1999
+ let json = serde_json::to_string(&policy).unwrap();
2000
+ assert_eq!(json, "\"wait_for_children\"");
2001
+ let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
2002
+ assert_eq!(policy, parsed);
2003
+ }
2004
+
2005
+ #[test]
2006
+ fn test_cancellation_policy_serde_detach() {
2007
+ let policy = CancellationPolicy::Detach;
2008
+ let json = serde_json::to_string(&policy).unwrap();
2009
+ assert_eq!(json, "\"detach\"");
2010
+ let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
2011
+ assert_eq!(policy, parsed);
2012
+ }
2013
+
2014
+ #[test]
2015
+ fn test_cancellation_policy_all_variants_serde() {
2016
+ let variants = vec![
2017
+ CancellationPolicy::CascadeCancel,
2018
+ CancellationPolicy::WaitForChildren,
2019
+ CancellationPolicy::Detach,
2020
+ ];
2021
+ for variant in variants {
2022
+ let json = serde_json::to_string(&variant).unwrap();
2023
+ let parsed: CancellationPolicy = serde_json::from_str(&json).unwrap();
2024
+ assert_eq!(parsed, variant);
2025
+ }
2026
+ }
2027
+
2028
+ #[test]
2029
+ fn test_cancellation_policy_clone() {
2030
+ let policy = CancellationPolicy::WaitForChildren;
2031
+ let cloned = policy.clone();
2032
+ assert_eq!(policy, cloned);
2033
+ }
2034
+
2035
+ #[test]
2036
+ fn test_cancellation_policy_hash() {
2037
+ let policy = CancellationPolicy::Detach;
2038
+ let mut set = HashSet::new();
2039
+ set.insert(policy.clone());
2040
+ assert!(set.contains(&policy));
2041
+ }
2042
+
2043
+ // =========================================================================
2044
+ // StepSource with SpawnMode Tests
2045
+ // =========================================================================
2046
+
2047
+ #[test]
2048
+ fn test_step_source_with_spawn_mode() {
2049
+ let source = StepSource {
2050
+ source_type: StepSourceType::Discovered,
2051
+ triggered_by: Some("step_parent".to_string()),
2052
+ reason: Some("test spawn".to_string()),
2053
+ depth: Some(1),
2054
+ spawn_mode: Some(SpawnMode::Child {
2055
+ background: false,
2056
+ inherit_inbox: true,
2057
+ policies: None,
2058
+ }),
2059
+ };
2060
+ assert!(source.spawn_mode.is_some());
2061
+ if let Some(SpawnMode::Child { inherit_inbox, .. }) = source.spawn_mode {
2062
+ assert!(inherit_inbox);
2063
+ }
2064
+ }
2065
+
2066
+ #[test]
2067
+ fn test_step_source_discovered_default_spawn_mode() {
2068
+ let spawner = StepId::new();
2069
+ let source = StepSource::discovered(&spawner, "test reason", 1);
2070
+ assert!(source.spawn_mode.is_none(), "discovered() should default to None spawn_mode");
2071
+ }
2072
+
2073
+ #[test]
2074
+ fn test_step_source_serde_with_spawn_mode() {
2075
+ let source = StepSource {
2076
+ source_type: StepSourceType::Discovered,
2077
+ triggered_by: Some("step_123".to_string()),
2078
+ reason: Some("test reason".to_string()),
2079
+ depth: Some(2),
2080
+ spawn_mode: Some(SpawnMode::Inline),
2081
+ };
2082
+ let json = serde_json::to_string(&source).unwrap();
2083
+ let parsed: StepSource = serde_json::from_str(&json).unwrap();
2084
+ assert_eq!(source, parsed);
2085
+ }
2086
+ }