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.
- package/.env.example +20 -0
- package/.github/workflows/ci.yml +70 -0
- package/.github/workflows/publish.yml +250 -0
- package/.gitmodules +3 -0
- package/Cargo.lock +3584 -0
- package/Cargo.toml +97 -0
- package/crates/enact/Cargo.toml +27 -0
- package/crates/enact/src/lib.rs +60 -0
- package/crates/enact-a2a/Cargo.toml +25 -0
- package/crates/enact-a2a/src/lib.rs +411 -0
- package/crates/enact-channels/Cargo.toml +64 -0
- package/crates/enact-channels/examples/README.md +80 -0
- package/crates/enact-channels/examples/channel_bot.rs +169 -0
- package/crates/enact-channels/examples/telegram-echo.rs +34 -0
- package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
- package/crates/enact-channels/src/config.rs +213 -0
- package/crates/enact-channels/src/lib.rs +25 -0
- package/crates/enact-channels/src/runtime.rs +237 -0
- package/crates/enact-channels/src/security/mod.rs +5 -0
- package/crates/enact-channels/src/security/pairing.rs +205 -0
- package/crates/enact-channels/src/teams.rs +601 -0
- package/crates/enact-channels/src/telegram.rs +2833 -0
- package/crates/enact-channels/src/traits.rs +200 -0
- package/crates/enact-channels/src/webhook.rs +262 -0
- package/crates/enact-channels/src/whatsapp.rs +310 -0
- package/crates/enact-cli/Cargo.toml +40 -0
- package/crates/enact-cli/src/commands/doctor.rs +62 -0
- package/crates/enact-cli/src/commands/mod.rs +3 -0
- package/crates/enact-cli/src/commands/run.rs +69 -0
- package/crates/enact-cli/src/commands/serve.rs +81 -0
- package/crates/enact-cli/src/config.rs +2 -0
- package/crates/enact-cli/src/main.rs +79 -0
- package/crates/enact-config/Cargo.toml +36 -0
- package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
- package/crates/enact-config/QUICK_REFERENCE.md +92 -0
- package/crates/enact-config/README.md +107 -0
- package/crates/enact-config/TESTING.md +161 -0
- package/crates/enact-config/examples/test-env-vars.rs +100 -0
- package/crates/enact-config/src/config.rs +399 -0
- package/crates/enact-config/src/encrypted_store.rs +211 -0
- package/crates/enact-config/src/lib.rs +298 -0
- package/crates/enact-config/src/secrets.rs +149 -0
- package/crates/enact-config/src/sync.rs +260 -0
- package/crates/enact-config/test-env-vars.sh +34 -0
- package/crates/enact-config/tests/README.md +99 -0
- package/crates/enact-config/tests/config_integration_test.rs +202 -0
- package/crates/enact-config/tests/security_test.rs +140 -0
- package/crates/enact-context/Cargo.toml +41 -0
- package/crates/enact-context/src/budget.rs +314 -0
- package/crates/enact-context/src/calibrator.rs +535 -0
- package/crates/enact-context/src/compactor.rs +392 -0
- package/crates/enact-context/src/condenser.rs +826 -0
- package/crates/enact-context/src/lib.rs +94 -0
- package/crates/enact-context/src/segment.rs +238 -0
- package/crates/enact-context/src/step_context.rs +645 -0
- package/crates/enact-context/src/token_counter.rs +148 -0
- package/crates/enact-context/src/window.rs +372 -0
- package/crates/enact-core/Cargo.toml +42 -0
- package/crates/enact-core/README.md +98 -0
- package/crates/enact-core/src/background/executor.rs +524 -0
- package/crates/enact-core/src/background/mod.rs +48 -0
- package/crates/enact-core/src/background/target_binding.rs +390 -0
- package/crates/enact-core/src/background/trigger.rs +511 -0
- package/crates/enact-core/src/callable/callable.rs +152 -0
- package/crates/enact-core/src/callable/composite.rs +817 -0
- package/crates/enact-core/src/callable/graph.rs +104 -0
- package/crates/enact-core/src/callable/llm.rs +211 -0
- package/crates/enact-core/src/callable/mod.rs +64 -0
- package/crates/enact-core/src/callable/registry.rs +206 -0
- package/crates/enact-core/src/context/execution_context.rs +757 -0
- package/crates/enact-core/src/context/invocation.rs +99 -0
- package/crates/enact-core/src/context/mod.rs +50 -0
- package/crates/enact-core/src/context/tenant.rs +175 -0
- package/crates/enact-core/src/context/trace.rs +127 -0
- package/crates/enact-core/src/flow/conditional.rs +293 -0
- package/crates/enact-core/src/flow/mod.rs +43 -0
- package/crates/enact-core/src/flow/parallel.rs +437 -0
- package/crates/enact-core/src/flow/repeat.rs +534 -0
- package/crates/enact-core/src/flow/sequential.rs +248 -0
- package/crates/enact-core/src/graph/checkpoint.rs +79 -0
- package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
- package/crates/enact-core/src/graph/compiled.rs +189 -0
- package/crates/enact-core/src/graph/edge.rs +59 -0
- package/crates/enact-core/src/graph/graph_schema.rs +218 -0
- package/crates/enact-core/src/graph/loader.rs +155 -0
- package/crates/enact-core/src/graph/mod.rs +18 -0
- package/crates/enact-core/src/graph/node/function.rs +49 -0
- package/crates/enact-core/src/graph/node/mod.rs +48 -0
- package/crates/enact-core/src/graph/schema.rs +62 -0
- package/crates/enact-core/src/inbox/message.rs +405 -0
- package/crates/enact-core/src/inbox/mod.rs +31 -0
- package/crates/enact-core/src/inbox/store.rs +355 -0
- package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
- package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
- package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
- package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
- package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
- package/crates/enact-core/src/kernel/error.rs +1200 -0
- package/crates/enact-core/src/kernel/event.rs +1394 -0
- package/crates/enact-core/src/kernel/execution_model.rs +831 -0
- package/crates/enact-core/src/kernel/execution_state.rs +189 -0
- package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
- package/crates/enact-core/src/kernel/ids.rs +2086 -0
- package/crates/enact-core/src/kernel/interrupt.rs +125 -0
- package/crates/enact-core/src/kernel/kernel.rs +1283 -0
- package/crates/enact-core/src/kernel/mod.rs +205 -0
- package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
- package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
- package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
- package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
- package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
- package/crates/enact-core/src/kernel/reducer.rs +808 -0
- package/crates/enact-core/src/kernel/replay.rs +153 -0
- package/crates/enact-core/src/lib.rs +413 -0
- package/crates/enact-core/src/memory/episodic.rs +0 -0
- package/crates/enact-core/src/memory/mod.rs +6 -0
- package/crates/enact-core/src/memory/semantic.rs +0 -0
- package/crates/enact-core/src/memory/trait.rs +0 -0
- package/crates/enact-core/src/memory/vector_db.rs +0 -0
- package/crates/enact-core/src/memory/working.rs +0 -0
- package/crates/enact-core/src/policy/execution_policy.rs +292 -0
- package/crates/enact-core/src/policy/filters.rs +458 -0
- package/crates/enact-core/src/policy/input_processor.rs +407 -0
- package/crates/enact-core/src/policy/long_running.rs +134 -0
- package/crates/enact-core/src/policy/mod.rs +193 -0
- package/crates/enact-core/src/policy/pii_input.rs +274 -0
- package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
- package/crates/enact-core/src/policy/tool_policy.rs +407 -0
- package/crates/enact-core/src/providers/mod.rs +63 -0
- package/crates/enact-core/src/providers/trait.rs +292 -0
- package/crates/enact-core/src/runner/callbacks.rs +6 -0
- package/crates/enact-core/src/runner/execution_runner.rs +476 -0
- package/crates/enact-core/src/runner/loop.rs +117 -0
- package/crates/enact-core/src/runner/mod.rs +58 -0
- package/crates/enact-core/src/runner/protected_runner.rs +280 -0
- package/crates/enact-core/src/signal/inmemory.rs +231 -0
- package/crates/enact-core/src/signal/mod.rs +108 -0
- package/crates/enact-core/src/streaming/event_logger.rs +195 -0
- package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
- package/crates/enact-core/src/streaming/mod.rs +108 -0
- package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
- package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
- package/crates/enact-core/src/streaming/protection/context.rs +136 -0
- package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
- package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
- package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
- package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
- package/crates/enact-core/src/streaming/sse.rs +0 -0
- package/crates/enact-core/src/telemetry/exporter.rs +0 -0
- package/crates/enact-core/src/telemetry/init.rs +0 -0
- package/crates/enact-core/src/telemetry/mod.rs +49 -0
- package/crates/enact-core/src/telemetry/spans.rs +245 -0
- package/crates/enact-core/src/tool/agent_tool.rs +177 -0
- package/crates/enact-core/src/tool/browser/mod.rs +0 -0
- package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
- package/crates/enact-core/src/tool/cost.rs +247 -0
- package/crates/enact-core/src/tool/discovery.rs +0 -0
- package/crates/enact-core/src/tool/dispatcher.rs +347 -0
- package/crates/enact-core/src/tool/filesystem.rs +231 -0
- package/crates/enact-core/src/tool/function.rs +99 -0
- package/crates/enact-core/src/tool/git.rs +162 -0
- package/crates/enact-core/src/tool/http.rs +214 -0
- package/crates/enact-core/src/tool/mcp/client.rs +0 -0
- package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
- package/crates/enact-core/src/tool/mod.rs +51 -0
- package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
- package/crates/enact-core/src/tool/shell.rs +147 -0
- package/crates/enact-core/src/tool/trait.rs +33 -0
- package/crates/enact-core/src/tool/web_search.rs +277 -0
- package/crates/enact-core/src/util/config.rs +0 -0
- package/crates/enact-core/src/util/errors.rs +0 -0
- package/crates/enact-core/src/util/mod.rs +6 -0
- package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
- package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
- package/crates/enact-core/tests/e2e_test.rs +259 -0
- package/crates/enact-core/tests/graph_test.rs +130 -0
- package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
- package/crates/enact-cron/Cargo.toml +28 -0
- package/crates/enact-cron/src/lib.rs +44 -0
- package/crates/enact-cron/src/schedule.rs +156 -0
- package/crates/enact-cron/src/store.rs +589 -0
- package/crates/enact-cron/src/types.rs +148 -0
- package/crates/enact-gateway/Cargo.toml +31 -0
- package/crates/enact-gateway/README.md +30 -0
- package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
- package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
- package/crates/enact-gateway/src/lib.rs +582 -0
- package/crates/enact-mcp/Cargo.toml +24 -0
- package/crates/enact-mcp/src/lib.rs +178 -0
- package/crates/enact-memory/Cargo.toml +25 -0
- package/crates/enact-memory/src/backend.rs +20 -0
- package/crates/enact-memory/src/chunker.rs +230 -0
- package/crates/enact-memory/src/embeddings.rs +221 -0
- package/crates/enact-memory/src/lib.rs +67 -0
- package/crates/enact-memory/src/markdown.rs +127 -0
- package/crates/enact-memory/src/none.rs +61 -0
- package/crates/enact-memory/src/sqlite.rs +276 -0
- package/crates/enact-memory/src/traits.rs +65 -0
- package/crates/enact-memory/src/vector.rs +198 -0
- package/crates/enact-oauth/Cargo.toml +27 -0
- package/crates/enact-oauth/src/lib.rs +584 -0
- package/crates/enact-observability/Cargo.toml +22 -0
- package/crates/enact-observability/src/lib.rs +197 -0
- package/crates/enact-providers/Cargo.toml +33 -0
- package/crates/enact-providers/examples/hello-agent.rs +33 -0
- package/crates/enact-providers/src/anthropic.rs +182 -0
- package/crates/enact-providers/src/azure.rs +96 -0
- package/crates/enact-providers/src/bridge.rs +221 -0
- package/crates/enact-providers/src/gemini.rs +227 -0
- package/crates/enact-providers/src/http.rs +78 -0
- package/crates/enact-providers/src/lib.rs +53 -0
- package/crates/enact-providers/src/openai_compatible.rs +167 -0
- package/crates/enact-providers/src/openrouter.rs +33 -0
- package/crates/enact-runner/Cargo.toml +24 -0
- package/crates/enact-runner/README.md +76 -0
- package/crates/enact-runner/src/compaction.rs +225 -0
- package/crates/enact-runner/src/config.rs +118 -0
- package/crates/enact-runner/src/lib.rs +63 -0
- package/crates/enact-runner/src/loop_driver.rs +414 -0
- package/crates/enact-runner/src/parser.rs +421 -0
- package/crates/enact-runner/src/retry.rs +262 -0
- package/crates/enact-runner/tests/integration.rs +278 -0
- package/crates/enact-security/Cargo.toml +22 -0
- package/crates/enact-security/src/audit.rs +375 -0
- package/crates/enact-security/src/lib.rs +37 -0
- package/crates/enact-security/src/policy.rs +406 -0
- package/crates/enact-skills/Cargo.toml +25 -0
- package/crates/enact-skills/src/lib.rs +506 -0
- package/crates/enact-tools/Cargo.toml +22 -0
- package/crates/enact-tools/src/file_read.rs +166 -0
- package/crates/enact-tools/src/file_write.rs +216 -0
- package/crates/enact-tools/src/git_operations.rs +513 -0
- package/crates/enact-tools/src/http_request.rs +417 -0
- package/crates/enact-tools/src/lib.rs +104 -0
- package/crates/enact-tools/src/security.rs +227 -0
- package/crates/enact-tools/src/shell.rs +191 -0
- package/crates/enact-tools/src/traits.rs +159 -0
- package/docs/Makefile +74 -0
- package/docs/config.toml +62 -0
- package/docs/content/_index.md +174 -0
- package/docs/content/a2a/_index.md +431 -0
- package/docs/content/api/_index.md +323 -0
- package/docs/content/channels/_index.md +160 -0
- package/docs/content/channels/teams.md +205 -0
- package/docs/content/channels/telegram.md +182 -0
- package/docs/content/channels/webhook.md +423 -0
- package/docs/content/channels/whatsapp.md +240 -0
- package/docs/content/cli/_index.md +261 -0
- package/docs/content/concepts/_index.md +273 -0
- package/docs/content/configuration/_index.md +241 -0
- package/docs/content/cron/_index.md +248 -0
- package/docs/content/developers/_index.md +278 -0
- package/docs/content/getting-started/_index.md +180 -0
- package/docs/content/installation/_index.md +186 -0
- package/docs/content/installation/uninstall.md +101 -0
- package/docs/content/installation/updating.md +120 -0
- package/docs/content/mcp/_index.md +215 -0
- package/docs/content/memory/_index.md +163 -0
- package/docs/content/oauth/_index.md +515 -0
- package/docs/content/providers/_index.md +206 -0
- package/docs/content/roadmap/_index.md +199 -0
- package/docs/content/security/_index.md +219 -0
- package/docs/content/skills/_index.md +228 -0
- package/docs/content/tools/_index.md +485 -0
- package/docs/content/troubleshooting/_index.md +259 -0
- package/docs/content/yaml-schema/_index.md +294 -0
- package/docs/static/giallo-dark.css +91 -0
- package/docs/static/giallo-light.css +91 -0
- package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
- package/docs/themes/tanuki/LICENSE +21 -0
- package/docs/themes/tanuki/README.md +166 -0
- package/docs/themes/tanuki/examples/blog/config.toml +58 -0
- package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
- package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
- package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
- package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
- package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
- package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
- package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
- package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
- package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
- package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
- package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
- package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
- package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
- package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
- package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
- package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
- package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
- package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
- package/docs/themes/tanuki/examples/book/config.toml +17 -0
- package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
- package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
- package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
- package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
- package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
- package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
- package/docs/themes/tanuki/examples/book/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/config.toml +28 -0
- package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
- package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
- package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
- package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
- package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
- package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
- package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/examples/index.html +2104 -0
- package/docs/themes/tanuki/mise.toml +108 -0
- package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
- package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
- package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
- package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
- package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
- package/docs/themes/tanuki/sass/components/_code.scss +457 -0
- package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
- package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
- package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
- package/docs/themes/tanuki/sass/components/_print.scss +237 -0
- package/docs/themes/tanuki/sass/components/_search.scss +224 -0
- package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
- package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
- package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
- package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
- package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
- package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
- package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
- package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
- package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
- package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
- package/docs/themes/tanuki/sass/style.scss +47 -0
- package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
- package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
- package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
- package/docs/themes/tanuki/screenshot.png +0 -0
- package/docs/themes/tanuki/sentinel.kdl +59 -0
- package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
- package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
- package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
- package/docs/themes/tanuki/static/js/anchors.js +18 -0
- package/docs/themes/tanuki/static/js/app.js +274 -0
- package/docs/themes/tanuki/static/js/code.js +394 -0
- package/docs/themes/tanuki/static/js/navigation.js +778 -0
- package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
- package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
- package/docs/themes/tanuki/static/js/search.js +215 -0
- package/docs/themes/tanuki/static/js/theme.js +169 -0
- package/docs/themes/tanuki/static/syntax-dark.css +151 -0
- package/docs/themes/tanuki/static/syntax-light.css +151 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
- package/docs/themes/tanuki/templates/404.html +52 -0
- package/docs/themes/tanuki/templates/base.html +428 -0
- package/docs/themes/tanuki/templates/blog.html +66 -0
- package/docs/themes/tanuki/templates/home.html +108 -0
- package/docs/themes/tanuki/templates/index.html +178 -0
- package/docs/themes/tanuki/templates/landing.html +168 -0
- package/docs/themes/tanuki/templates/macros/nav.html +128 -0
- package/docs/themes/tanuki/templates/macros/posts.html +101 -0
- package/docs/themes/tanuki/templates/macros/ui.html +159 -0
- package/docs/themes/tanuki/templates/page.html +135 -0
- package/docs/themes/tanuki/templates/partials/footer.html +38 -0
- package/docs/themes/tanuki/templates/partials/header.html +366 -0
- package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
- package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
- package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
- package/docs/themes/tanuki/templates/partials/search.html +52 -0
- package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
- package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
- package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
- package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
- package/docs/themes/tanuki/templates/print.html +244 -0
- package/docs/themes/tanuki/templates/section.html +186 -0
- package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
- package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
- package/docs/themes/tanuki/theme.toml +58 -0
- package/examples/hello-agent.rs +55 -0
- package/package.json +36 -0
- package/proto/config.proto +60 -0
- package/proto/events.proto +0 -0
- package/proto/runtime.proto +215 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
//! Integration tests for enact-runner
|
|
2
|
+
//!
|
|
3
|
+
//! These tests demonstrate how to use `AgentRunner` with mock callables and tools,
|
|
4
|
+
//! showing the full loop in action.
|
|
5
|
+
|
|
6
|
+
use async_trait::async_trait;
|
|
7
|
+
use enact_core::callable::Callable;
|
|
8
|
+
use enact_core::tool::Tool;
|
|
9
|
+
use enact_runner::{AgentRunner, DefaultAgentRunner, LoopOutcome, RunnerConfig};
|
|
10
|
+
use serde_json::{json, Value};
|
|
11
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
12
|
+
use std::sync::Arc;
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Mock Callable — simulates an LLM that uses tools
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
/// A mock LLM that returns tool calls for the first N iterations,
|
|
19
|
+
/// then returns a final text response.
|
|
20
|
+
struct MockLlm {
|
|
21
|
+
call_count: AtomicUsize,
|
|
22
|
+
tool_calls_before_done: usize,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl MockLlm {
|
|
26
|
+
fn new(tool_calls_before_done: usize) -> Self {
|
|
27
|
+
Self {
|
|
28
|
+
call_count: AtomicUsize::new(0),
|
|
29
|
+
tool_calls_before_done,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[async_trait]
|
|
35
|
+
impl Callable for MockLlm {
|
|
36
|
+
fn name(&self) -> &str {
|
|
37
|
+
"mock-llm"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async fn run(&self, _input: &str) -> anyhow::Result<String> {
|
|
41
|
+
let count = self.call_count.fetch_add(1, Ordering::SeqCst);
|
|
42
|
+
|
|
43
|
+
if count < self.tool_calls_before_done {
|
|
44
|
+
// Return a JSON tool call
|
|
45
|
+
Ok(json!({
|
|
46
|
+
"tool_call": {
|
|
47
|
+
"name": "search",
|
|
48
|
+
"arguments": {"query": format!("iteration {}", count)}
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
.to_string())
|
|
52
|
+
} else {
|
|
53
|
+
// Final response — no tool call
|
|
54
|
+
Ok(format!(
|
|
55
|
+
"Done! Completed {} tool calls before finishing.",
|
|
56
|
+
count
|
|
57
|
+
))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// A mock LLM that returns XML-format tool calls.
|
|
63
|
+
struct MockXmlLlm {
|
|
64
|
+
call_count: AtomicUsize,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
impl MockXmlLlm {
|
|
68
|
+
fn new() -> Self {
|
|
69
|
+
Self {
|
|
70
|
+
call_count: AtomicUsize::new(0),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[async_trait]
|
|
76
|
+
impl Callable for MockXmlLlm {
|
|
77
|
+
fn name(&self) -> &str {
|
|
78
|
+
"mock-xml-llm"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async fn run(&self, _input: &str) -> anyhow::Result<String> {
|
|
82
|
+
let count = self.call_count.fetch_add(1, Ordering::SeqCst);
|
|
83
|
+
|
|
84
|
+
if count == 0 {
|
|
85
|
+
Ok(r#"Let me search for that.
|
|
86
|
+
<tool_call><name>search</name><arguments>{"query": "rust async"}</arguments></tool_call>"#
|
|
87
|
+
.to_string())
|
|
88
|
+
} else {
|
|
89
|
+
Ok("Here are the results for your query about Rust async.".to_string())
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// A mock LLM that always fails (for retry testing).
|
|
95
|
+
struct FailingLlm {
|
|
96
|
+
call_count: AtomicUsize,
|
|
97
|
+
fail_count: usize,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl FailingLlm {
|
|
101
|
+
fn new(fail_count: usize) -> Self {
|
|
102
|
+
Self {
|
|
103
|
+
call_count: AtomicUsize::new(0),
|
|
104
|
+
fail_count,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[async_trait]
|
|
110
|
+
impl Callable for FailingLlm {
|
|
111
|
+
fn name(&self) -> &str {
|
|
112
|
+
"failing-llm"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async fn run(&self, _input: &str) -> anyhow::Result<String> {
|
|
116
|
+
let count = self.call_count.fetch_add(1, Ordering::SeqCst);
|
|
117
|
+
|
|
118
|
+
if count < self.fail_count {
|
|
119
|
+
anyhow::bail!("Rate limit exceeded, please retry after 1s")
|
|
120
|
+
} else {
|
|
121
|
+
Ok("Success after retries!".to_string())
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =============================================================================
|
|
127
|
+
// Mock Tool
|
|
128
|
+
// =============================================================================
|
|
129
|
+
|
|
130
|
+
struct MockSearchTool;
|
|
131
|
+
|
|
132
|
+
#[async_trait]
|
|
133
|
+
impl Tool for MockSearchTool {
|
|
134
|
+
fn name(&self) -> &str {
|
|
135
|
+
"search"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn description(&self) -> &str {
|
|
139
|
+
"Search for information"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn parameters_schema(&self) -> Value {
|
|
143
|
+
json!({
|
|
144
|
+
"type": "object",
|
|
145
|
+
"properties": {
|
|
146
|
+
"query": {"type": "string"}
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async fn execute(&self, args: Value) -> anyhow::Result<Value> {
|
|
152
|
+
let query = args
|
|
153
|
+
.get("query")
|
|
154
|
+
.and_then(|q| q.as_str())
|
|
155
|
+
.unwrap_or("unknown");
|
|
156
|
+
Ok(json!({
|
|
157
|
+
"results": [format!("Result for: {}", query)],
|
|
158
|
+
"count": 1
|
|
159
|
+
}))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// =============================================================================
|
|
164
|
+
// Tests
|
|
165
|
+
// =============================================================================
|
|
166
|
+
|
|
167
|
+
#[tokio::test]
|
|
168
|
+
async fn test_simple_no_tool_calls() {
|
|
169
|
+
// LLM returns final response immediately (0 tool calls before done)
|
|
170
|
+
let llm = MockLlm::new(0);
|
|
171
|
+
let mut runner = DefaultAgentRunner::default_new();
|
|
172
|
+
|
|
173
|
+
let outcome = runner.run(&llm, "Hello!").await.unwrap();
|
|
174
|
+
|
|
175
|
+
assert!(outcome.is_completed());
|
|
176
|
+
assert!(outcome.output().unwrap().contains("Done!"));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[tokio::test]
|
|
180
|
+
async fn test_tool_call_loop_json() {
|
|
181
|
+
// LLM makes 3 tool calls, then returns final response
|
|
182
|
+
let llm = MockLlm::new(3);
|
|
183
|
+
let mut runner = DefaultAgentRunner::default_new().add_tool(MockSearchTool);
|
|
184
|
+
|
|
185
|
+
let outcome = runner.run(&llm, "Search for Rust").await.unwrap();
|
|
186
|
+
|
|
187
|
+
assert!(outcome.is_completed());
|
|
188
|
+
let output = outcome.output().unwrap();
|
|
189
|
+
assert!(output.contains("3 tool calls"));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[tokio::test]
|
|
193
|
+
async fn test_tool_call_loop_xml() {
|
|
194
|
+
// LLM returns XML-format tool call, then final response
|
|
195
|
+
let llm = MockXmlLlm::new();
|
|
196
|
+
let mut runner = DefaultAgentRunner::default_new().add_tool(MockSearchTool);
|
|
197
|
+
|
|
198
|
+
let outcome = runner.run(&llm, "Search for Rust async").await.unwrap();
|
|
199
|
+
|
|
200
|
+
assert!(outcome.is_completed());
|
|
201
|
+
assert!(outcome.output().unwrap().contains("Rust async"));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[tokio::test]
|
|
205
|
+
async fn test_max_iterations_reached() {
|
|
206
|
+
// LLM always returns tool calls — loop should hit max_iterations
|
|
207
|
+
let llm = MockLlm::new(1000); // Will never finish
|
|
208
|
+
let config = RunnerConfig {
|
|
209
|
+
max_iterations: 3,
|
|
210
|
+
..Default::default()
|
|
211
|
+
};
|
|
212
|
+
let mut runner = DefaultAgentRunner::with_config(config).add_tool(MockSearchTool);
|
|
213
|
+
|
|
214
|
+
let outcome = runner.run(&llm, "Loop forever").await.unwrap();
|
|
215
|
+
|
|
216
|
+
assert!(!outcome.is_completed());
|
|
217
|
+
match outcome {
|
|
218
|
+
LoopOutcome::MaxIterationsReached { iterations, .. } => {
|
|
219
|
+
assert_eq!(iterations, 3);
|
|
220
|
+
}
|
|
221
|
+
other => panic!("Expected MaxIterationsReached, got {:?}", other),
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[tokio::test]
|
|
226
|
+
async fn test_retry_on_transient_error() {
|
|
227
|
+
// LLM fails twice with rate-limit error, then succeeds
|
|
228
|
+
let llm = FailingLlm::new(2);
|
|
229
|
+
let config = RunnerConfig {
|
|
230
|
+
retry: enact_runner::RetryConfig {
|
|
231
|
+
max_retries: 3,
|
|
232
|
+
initial_delay: std::time::Duration::from_millis(10), // Fast for tests
|
|
233
|
+
max_delay: std::time::Duration::from_millis(100),
|
|
234
|
+
backoff_multiplier: 2.0,
|
|
235
|
+
},
|
|
236
|
+
..Default::default()
|
|
237
|
+
};
|
|
238
|
+
let mut runner = DefaultAgentRunner::with_config(config);
|
|
239
|
+
|
|
240
|
+
let outcome = runner.run(&llm, "Will fail then succeed").await.unwrap();
|
|
241
|
+
|
|
242
|
+
assert!(outcome.is_completed());
|
|
243
|
+
assert!(outcome.output().unwrap().contains("Success after retries"));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[tokio::test]
|
|
247
|
+
async fn test_missing_tool_handled_gracefully() {
|
|
248
|
+
// LLM calls a tool that doesn't exist — runner should report missing tool
|
|
249
|
+
let llm = MockLlm::new(1);
|
|
250
|
+
// No tools registered!
|
|
251
|
+
let config = RunnerConfig {
|
|
252
|
+
max_iterations: 3,
|
|
253
|
+
..Default::default()
|
|
254
|
+
};
|
|
255
|
+
let mut runner = DefaultAgentRunner::with_config(config);
|
|
256
|
+
|
|
257
|
+
// Should NOT panic — the loop handles missing tools gracefully
|
|
258
|
+
let outcome = runner.run(&llm, "Call missing tool").await.unwrap();
|
|
259
|
+
|
|
260
|
+
// It should eventually complete (the "tool not found" message gets fed back)
|
|
261
|
+
assert!(outcome.output().is_some());
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[tokio::test]
|
|
265
|
+
async fn test_cancellation() {
|
|
266
|
+
let llm = MockLlm::new(100);
|
|
267
|
+
let mut runner = DefaultAgentRunner::default_new().add_tool(MockSearchTool);
|
|
268
|
+
|
|
269
|
+
// Cancel immediately
|
|
270
|
+
runner.inner().cancel();
|
|
271
|
+
|
|
272
|
+
let outcome = runner.run(&llm, "Will be cancelled").await.unwrap();
|
|
273
|
+
|
|
274
|
+
match outcome {
|
|
275
|
+
LoopOutcome::Cancelled => {} // Expected
|
|
276
|
+
other => panic!("Expected Cancelled, got {:?}", other),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "enact-security"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
description = "Security policy, audit logging, and sandboxing for Enact agents"
|
|
7
|
+
repository.workspace = true
|
|
8
|
+
homepage.workspace = true
|
|
9
|
+
keywords = ["security", "sandbox", "audit", "policy"]
|
|
10
|
+
categories.workspace = true
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
anyhow.workspace = true
|
|
14
|
+
chrono.workspace = true
|
|
15
|
+
serde.workspace = true
|
|
16
|
+
serde_json.workspace = true
|
|
17
|
+
tracing.workspace = true
|
|
18
|
+
uuid.workspace = true
|
|
19
|
+
parking_lot = "0.12"
|
|
20
|
+
|
|
21
|
+
[dev-dependencies]
|
|
22
|
+
tempfile.workspace = true
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
//! Audit logging for security events
|
|
2
|
+
//!
|
|
3
|
+
//! Provides structured logging of security-relevant events with rotation support.
|
|
4
|
+
|
|
5
|
+
use anyhow::Result;
|
|
6
|
+
use chrono::{DateTime, Utc};
|
|
7
|
+
use parking_lot::Mutex;
|
|
8
|
+
use serde::{Deserialize, Serialize};
|
|
9
|
+
use std::fs::OpenOptions;
|
|
10
|
+
use std::io::Write;
|
|
11
|
+
use std::path::PathBuf;
|
|
12
|
+
use uuid::Uuid;
|
|
13
|
+
|
|
14
|
+
/// Audit event types
|
|
15
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
16
|
+
#[serde(rename_all = "snake_case")]
|
|
17
|
+
pub enum AuditEventType {
|
|
18
|
+
CommandExecution,
|
|
19
|
+
FileAccess,
|
|
20
|
+
ConfigChange,
|
|
21
|
+
AuthSuccess,
|
|
22
|
+
AuthFailure,
|
|
23
|
+
PolicyViolation,
|
|
24
|
+
SecurityEvent,
|
|
25
|
+
ToolInvocation,
|
|
26
|
+
AgentAction,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Actor information (who performed the action)
|
|
30
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
31
|
+
pub struct Actor {
|
|
32
|
+
pub channel: String,
|
|
33
|
+
pub user_id: Option<String>,
|
|
34
|
+
pub username: Option<String>,
|
|
35
|
+
pub session_id: Option<String>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Action information (what was done)
|
|
39
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
40
|
+
pub struct Action {
|
|
41
|
+
pub command: Option<String>,
|
|
42
|
+
pub tool_name: Option<String>,
|
|
43
|
+
pub risk_level: Option<String>,
|
|
44
|
+
pub approved: bool,
|
|
45
|
+
pub allowed: bool,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Execution result
|
|
49
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
50
|
+
pub struct ExecutionResult {
|
|
51
|
+
pub success: bool,
|
|
52
|
+
pub exit_code: Option<i32>,
|
|
53
|
+
pub duration_ms: Option<u64>,
|
|
54
|
+
pub error: Option<String>,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Security context
|
|
58
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
59
|
+
pub struct SecurityContext {
|
|
60
|
+
pub policy_violation: bool,
|
|
61
|
+
pub rate_limit_remaining: Option<u32>,
|
|
62
|
+
pub autonomy_level: Option<String>,
|
|
63
|
+
pub sandbox_backend: Option<String>,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl Default for SecurityContext {
|
|
67
|
+
fn default() -> Self {
|
|
68
|
+
Self {
|
|
69
|
+
policy_violation: false,
|
|
70
|
+
rate_limit_remaining: None,
|
|
71
|
+
autonomy_level: None,
|
|
72
|
+
sandbox_backend: None,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Complete audit event
|
|
78
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
79
|
+
pub struct AuditEvent {
|
|
80
|
+
pub timestamp: DateTime<Utc>,
|
|
81
|
+
pub event_id: String,
|
|
82
|
+
pub event_type: AuditEventType,
|
|
83
|
+
pub actor: Option<Actor>,
|
|
84
|
+
pub action: Option<Action>,
|
|
85
|
+
pub result: Option<ExecutionResult>,
|
|
86
|
+
pub security: SecurityContext,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl AuditEvent {
|
|
90
|
+
/// Create a new audit event
|
|
91
|
+
pub fn new(event_type: AuditEventType) -> Self {
|
|
92
|
+
Self {
|
|
93
|
+
timestamp: Utc::now(),
|
|
94
|
+
event_id: Uuid::new_v4().to_string(),
|
|
95
|
+
event_type,
|
|
96
|
+
actor: None,
|
|
97
|
+
action: None,
|
|
98
|
+
result: None,
|
|
99
|
+
security: SecurityContext::default(),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Set the actor
|
|
104
|
+
pub fn with_actor(mut self, actor: Actor) -> Self {
|
|
105
|
+
self.actor = Some(actor);
|
|
106
|
+
self
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Set the action
|
|
110
|
+
pub fn with_action(mut self, action: Action) -> Self {
|
|
111
|
+
self.action = Some(action);
|
|
112
|
+
self
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Set the result
|
|
116
|
+
pub fn with_result(mut self, result: ExecutionResult) -> Self {
|
|
117
|
+
self.result = Some(result);
|
|
118
|
+
self
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Set security context
|
|
122
|
+
pub fn with_security(mut self, security: SecurityContext) -> Self {
|
|
123
|
+
self.security = security;
|
|
124
|
+
self
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Mark as policy violation
|
|
128
|
+
pub fn as_violation(mut self) -> Self {
|
|
129
|
+
self.security.policy_violation = true;
|
|
130
|
+
self
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Audit logger configuration
|
|
135
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
136
|
+
pub struct AuditConfig {
|
|
137
|
+
pub enabled: bool,
|
|
138
|
+
pub log_path: String,
|
|
139
|
+
pub max_size_mb: u32,
|
|
140
|
+
pub retain_days: u32,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
impl Default for AuditConfig {
|
|
144
|
+
fn default() -> Self {
|
|
145
|
+
Self {
|
|
146
|
+
enabled: true,
|
|
147
|
+
log_path: "audit.log".to_string(),
|
|
148
|
+
max_size_mb: 100,
|
|
149
|
+
retain_days: 90,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Audit logger with rotation support
|
|
155
|
+
pub struct AuditLogger {
|
|
156
|
+
log_path: PathBuf,
|
|
157
|
+
config: AuditConfig,
|
|
158
|
+
buffer: Mutex<Vec<AuditEvent>>,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
impl AuditLogger {
|
|
162
|
+
/// Create a new audit logger
|
|
163
|
+
pub fn new(config: AuditConfig, workspace_dir: PathBuf) -> Result<Self> {
|
|
164
|
+
let log_path = workspace_dir.join(&config.log_path);
|
|
165
|
+
Ok(Self {
|
|
166
|
+
log_path,
|
|
167
|
+
config,
|
|
168
|
+
buffer: Mutex::new(Vec::new()),
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Log an event
|
|
173
|
+
pub fn log(&self, event: &AuditEvent) -> Result<()> {
|
|
174
|
+
if !self.config.enabled {
|
|
175
|
+
return Ok(());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
self.rotate_if_needed()?;
|
|
179
|
+
|
|
180
|
+
let line = serde_json::to_string(event)?;
|
|
181
|
+
let mut file = OpenOptions::new()
|
|
182
|
+
.create(true)
|
|
183
|
+
.append(true)
|
|
184
|
+
.open(&self.log_path)?;
|
|
185
|
+
|
|
186
|
+
writeln!(file, "{}", line)?;
|
|
187
|
+
file.sync_all()?;
|
|
188
|
+
|
|
189
|
+
Ok(())
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Log a command execution
|
|
193
|
+
pub fn log_command(
|
|
194
|
+
&self,
|
|
195
|
+
channel: &str,
|
|
196
|
+
command: &str,
|
|
197
|
+
risk_level: &str,
|
|
198
|
+
approved: bool,
|
|
199
|
+
allowed: bool,
|
|
200
|
+
success: bool,
|
|
201
|
+
duration_ms: u64,
|
|
202
|
+
) -> Result<()> {
|
|
203
|
+
let event = AuditEvent::new(AuditEventType::CommandExecution)
|
|
204
|
+
.with_actor(Actor {
|
|
205
|
+
channel: channel.to_string(),
|
|
206
|
+
user_id: None,
|
|
207
|
+
username: None,
|
|
208
|
+
session_id: None,
|
|
209
|
+
})
|
|
210
|
+
.with_action(Action {
|
|
211
|
+
command: Some(command.to_string()),
|
|
212
|
+
tool_name: None,
|
|
213
|
+
risk_level: Some(risk_level.to_string()),
|
|
214
|
+
approved,
|
|
215
|
+
allowed,
|
|
216
|
+
})
|
|
217
|
+
.with_result(ExecutionResult {
|
|
218
|
+
success,
|
|
219
|
+
exit_code: None,
|
|
220
|
+
duration_ms: Some(duration_ms),
|
|
221
|
+
error: None,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
self.log(&event)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// Log a tool invocation
|
|
228
|
+
pub fn log_tool(
|
|
229
|
+
&self,
|
|
230
|
+
channel: &str,
|
|
231
|
+
tool_name: &str,
|
|
232
|
+
allowed: bool,
|
|
233
|
+
success: bool,
|
|
234
|
+
duration_ms: u64,
|
|
235
|
+
error: Option<&str>,
|
|
236
|
+
) -> Result<()> {
|
|
237
|
+
let event = AuditEvent::new(AuditEventType::ToolInvocation)
|
|
238
|
+
.with_actor(Actor {
|
|
239
|
+
channel: channel.to_string(),
|
|
240
|
+
user_id: None,
|
|
241
|
+
username: None,
|
|
242
|
+
session_id: None,
|
|
243
|
+
})
|
|
244
|
+
.with_action(Action {
|
|
245
|
+
command: None,
|
|
246
|
+
tool_name: Some(tool_name.to_string()),
|
|
247
|
+
risk_level: None,
|
|
248
|
+
approved: true,
|
|
249
|
+
allowed,
|
|
250
|
+
})
|
|
251
|
+
.with_result(ExecutionResult {
|
|
252
|
+
success,
|
|
253
|
+
exit_code: None,
|
|
254
|
+
duration_ms: Some(duration_ms),
|
|
255
|
+
error: error.map(String::from),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
self.log(&event)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/// Log a policy violation
|
|
262
|
+
pub fn log_violation(&self, channel: &str, description: &str) -> Result<()> {
|
|
263
|
+
let event = AuditEvent::new(AuditEventType::PolicyViolation)
|
|
264
|
+
.with_actor(Actor {
|
|
265
|
+
channel: channel.to_string(),
|
|
266
|
+
user_id: None,
|
|
267
|
+
username: None,
|
|
268
|
+
session_id: None,
|
|
269
|
+
})
|
|
270
|
+
.with_action(Action {
|
|
271
|
+
command: Some(description.to_string()),
|
|
272
|
+
tool_name: None,
|
|
273
|
+
risk_level: Some("high".to_string()),
|
|
274
|
+
approved: false,
|
|
275
|
+
allowed: false,
|
|
276
|
+
})
|
|
277
|
+
.as_violation();
|
|
278
|
+
|
|
279
|
+
self.log(&event)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// Check if rotation is needed
|
|
283
|
+
fn rotate_if_needed(&self) -> Result<()> {
|
|
284
|
+
if let Ok(metadata) = std::fs::metadata(&self.log_path) {
|
|
285
|
+
let current_size_mb = metadata.len() / (1024 * 1024);
|
|
286
|
+
if current_size_mb >= u64::from(self.config.max_size_mb) {
|
|
287
|
+
self.rotate()?;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
Ok(())
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/// Rotate the log file
|
|
294
|
+
fn rotate(&self) -> Result<()> {
|
|
295
|
+
for i in (1..10).rev() {
|
|
296
|
+
let old_name = format!("{}.{}.log", self.log_path.display(), i);
|
|
297
|
+
let new_name = format!("{}.{}.log", self.log_path.display(), i + 1);
|
|
298
|
+
let _ = std::fs::rename(&old_name, &new_name);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let rotated = format!("{}.1.log", self.log_path.display());
|
|
302
|
+
std::fs::rename(&self.log_path, &rotated)?;
|
|
303
|
+
Ok(())
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#[cfg(test)]
|
|
308
|
+
mod tests {
|
|
309
|
+
use super::*;
|
|
310
|
+
use tempfile::TempDir;
|
|
311
|
+
|
|
312
|
+
#[test]
|
|
313
|
+
fn audit_event_new_creates_unique_id() {
|
|
314
|
+
let event1 = AuditEvent::new(AuditEventType::CommandExecution);
|
|
315
|
+
let event2 = AuditEvent::new(AuditEventType::CommandExecution);
|
|
316
|
+
assert_ne!(event1.event_id, event2.event_id);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[test]
|
|
320
|
+
fn audit_event_serializes_to_json() {
|
|
321
|
+
let event = AuditEvent::new(AuditEventType::CommandExecution)
|
|
322
|
+
.with_actor(Actor {
|
|
323
|
+
channel: "telegram".to_string(),
|
|
324
|
+
user_id: None,
|
|
325
|
+
username: None,
|
|
326
|
+
session_id: None,
|
|
327
|
+
})
|
|
328
|
+
.with_action(Action {
|
|
329
|
+
command: Some("ls".to_string()),
|
|
330
|
+
tool_name: None,
|
|
331
|
+
risk_level: Some("low".to_string()),
|
|
332
|
+
approved: false,
|
|
333
|
+
allowed: true,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
let json = serde_json::to_string(&event);
|
|
337
|
+
assert!(json.is_ok());
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[test]
|
|
341
|
+
fn audit_logger_disabled_does_not_create_file() -> Result<()> {
|
|
342
|
+
let tmp = TempDir::new()?;
|
|
343
|
+
let config = AuditConfig {
|
|
344
|
+
enabled: false,
|
|
345
|
+
..Default::default()
|
|
346
|
+
};
|
|
347
|
+
let logger = AuditLogger::new(config, tmp.path().to_path_buf())?;
|
|
348
|
+
let event = AuditEvent::new(AuditEventType::CommandExecution);
|
|
349
|
+
|
|
350
|
+
logger.log(&event)?;
|
|
351
|
+
|
|
352
|
+
assert!(!tmp.path().join("audit.log").exists());
|
|
353
|
+
Ok(())
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
#[test]
|
|
357
|
+
fn audit_logger_writes_event_when_enabled() -> Result<()> {
|
|
358
|
+
let tmp = TempDir::new()?;
|
|
359
|
+
let config = AuditConfig {
|
|
360
|
+
enabled: true,
|
|
361
|
+
max_size_mb: 10,
|
|
362
|
+
..Default::default()
|
|
363
|
+
};
|
|
364
|
+
let logger = AuditLogger::new(config, tmp.path().to_path_buf())?;
|
|
365
|
+
|
|
366
|
+
logger.log_command("cli", "ls", "low", false, true, true, 15)?;
|
|
367
|
+
|
|
368
|
+
let log_path = tmp.path().join("audit.log");
|
|
369
|
+
assert!(log_path.exists());
|
|
370
|
+
|
|
371
|
+
let content = std::fs::read_to_string(&log_path)?;
|
|
372
|
+
assert!(!content.is_empty());
|
|
373
|
+
Ok(())
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//! Security policy, audit logging, and sandboxing for Enact agents
|
|
2
|
+
//!
|
|
3
|
+
//! This crate provides:
|
|
4
|
+
//! - Security policy with autonomy levels
|
|
5
|
+
//! - Rate limiting
|
|
6
|
+
//! - Audit logging with rotation
|
|
7
|
+
//! - Action validation
|
|
8
|
+
|
|
9
|
+
pub mod audit;
|
|
10
|
+
pub mod policy;
|
|
11
|
+
|
|
12
|
+
pub use audit::{
|
|
13
|
+
Action, Actor, AuditConfig, AuditEvent, AuditEventType, AuditLogger, ExecutionResult,
|
|
14
|
+
SecurityContext,
|
|
15
|
+
};
|
|
16
|
+
pub use policy::{
|
|
17
|
+
ActionValidation, AutonomyLevel, PolicyConfig, RiskLevel, SecurityPolicy,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
#[cfg(test)]
|
|
21
|
+
mod tests {
|
|
22
|
+
use super::*;
|
|
23
|
+
use std::path::PathBuf;
|
|
24
|
+
|
|
25
|
+
#[test]
|
|
26
|
+
fn policy_and_audit_integration() {
|
|
27
|
+
let policy = SecurityPolicy::default_for(PathBuf::from("/tmp/test"));
|
|
28
|
+
assert!(policy.can_act());
|
|
29
|
+
assert_eq!(policy.autonomy(), AutonomyLevel::Supervised);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[test]
|
|
33
|
+
fn audit_event_types() {
|
|
34
|
+
let event = AuditEvent::new(AuditEventType::CommandExecution);
|
|
35
|
+
assert!(!event.event_id.is_empty());
|
|
36
|
+
}
|
|
37
|
+
}
|