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,277 @@
|
|
|
1
|
+
//! Web search tool
|
|
2
|
+
|
|
3
|
+
use crate::tool::Tool;
|
|
4
|
+
use async_trait::async_trait;
|
|
5
|
+
use serde_json::json;
|
|
6
|
+
use std::time::Duration;
|
|
7
|
+
|
|
8
|
+
const DEFAULT_MAX_RESULTS: usize = 5;
|
|
9
|
+
const DEFAULT_TIMEOUT_SECS: u64 = 30;
|
|
10
|
+
|
|
11
|
+
/// Web search tool supporting DuckDuckGo (free) and Brave (API key required)
|
|
12
|
+
pub struct WebSearchTool {
|
|
13
|
+
provider: String,
|
|
14
|
+
brave_api_key: Option<String>,
|
|
15
|
+
max_results: usize,
|
|
16
|
+
timeout_secs: u64,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl WebSearchTool {
|
|
20
|
+
pub fn new(provider: impl Into<String>) -> Self {
|
|
21
|
+
Self {
|
|
22
|
+
provider: provider.into().to_lowercase(),
|
|
23
|
+
brave_api_key: None,
|
|
24
|
+
max_results: DEFAULT_MAX_RESULTS,
|
|
25
|
+
timeout_secs: DEFAULT_TIMEOUT_SECS,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub fn with_brave_key(mut self, key: impl Into<String>) -> Self {
|
|
30
|
+
self.brave_api_key = Some(key.into());
|
|
31
|
+
self
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub fn with_max_results(mut self, max: usize) -> Self {
|
|
35
|
+
self.max_results = max.clamp(1, 10);
|
|
36
|
+
self
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn with_timeout(mut self, secs: u64) -> Self {
|
|
40
|
+
self.timeout_secs = secs.max(1);
|
|
41
|
+
self
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async fn search_duckduckgo(&self,
|
|
45
|
+
query: &str,
|
|
46
|
+
) -> anyhow::Result<Vec<SearchResult>> {
|
|
47
|
+
let encoded_query = urlencoding::encode(query);
|
|
48
|
+
let search_url = format!(
|
|
49
|
+
"https://html.duckduckgo.com/html/?q={}",
|
|
50
|
+
encoded_query
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
let client = reqwest::Client::builder()
|
|
54
|
+
.timeout(Duration::from_secs(self.timeout_secs))
|
|
55
|
+
.user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36")
|
|
56
|
+
.build()?;
|
|
57
|
+
|
|
58
|
+
let response = client.get(&search_url).send().await?;
|
|
59
|
+
|
|
60
|
+
if !response.status().is_success() {
|
|
61
|
+
anyhow::bail!(
|
|
62
|
+
"DuckDuckGo search failed: {}",
|
|
63
|
+
response.status()
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let html = response.text().await?;
|
|
68
|
+
self.parse_duckduckgo_results(&html)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn parse_duckduckgo_results(&self,
|
|
72
|
+
html: &str,
|
|
73
|
+
) -> anyhow::Result<Vec<SearchResult>> {
|
|
74
|
+
let mut results = Vec::new();
|
|
75
|
+
|
|
76
|
+
// Simple regex-based parsing (in production, use a proper HTML parser)
|
|
77
|
+
let result_regex = regex::Regex::new(
|
|
78
|
+
r#"class="result__a"[^>]*href="([^"]+)"[^>]*>([^<]+)"#,
|
|
79
|
+
)?;
|
|
80
|
+
|
|
81
|
+
for cap in result_regex.captures_iter(html) {
|
|
82
|
+
if results.len() >= self.max_results {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let url = cap.get(1).map(|m| m.as_str()).unwrap_or("");
|
|
87
|
+
let title = cap.get(2).map(|m| m.as_str()).unwrap_or("");
|
|
88
|
+
|
|
89
|
+
// Clean up the URL (DuckDuckGo uses redirects)
|
|
90
|
+
let url = if url.starts_with("//duckduckgo.com/l/?") {
|
|
91
|
+
// Extract actual URL from redirect
|
|
92
|
+
url.split("uddg=")
|
|
93
|
+
.nth(1)
|
|
94
|
+
.and_then(|u| urlencoding::decode(u).ok())
|
|
95
|
+
.map(|s| s.to_string())
|
|
96
|
+
.unwrap_or_else(|| url.to_string())
|
|
97
|
+
} else {
|
|
98
|
+
url.to_string()
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let mut decoded_title = String::new();
|
|
102
|
+
html_escape::decode_html_entities_to_string(title, &mut decoded_title);
|
|
103
|
+
results.push(SearchResult {
|
|
104
|
+
title: decoded_title,
|
|
105
|
+
url,
|
|
106
|
+
snippet: String::new(),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Ok(results)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async fn search_brave(&self, query: &str) -> anyhow::Result<Vec<SearchResult>> {
|
|
114
|
+
let api_key = self
|
|
115
|
+
.brave_api_key
|
|
116
|
+
.as_ref()
|
|
117
|
+
.ok_or_else(|| anyhow::anyhow!("Brave API key required"))?;
|
|
118
|
+
|
|
119
|
+
let client = reqwest::Client::builder()
|
|
120
|
+
.timeout(Duration::from_secs(self.timeout_secs))
|
|
121
|
+
.build()?;
|
|
122
|
+
|
|
123
|
+
let response = client
|
|
124
|
+
.get("https://api.search.brave.com/res/v1/web/search")
|
|
125
|
+
.header("Accept", "application/json")
|
|
126
|
+
.header("X-Subscription-Token", api_key)
|
|
127
|
+
.query(&[("q", query), ("count", &self.max_results.to_string())])
|
|
128
|
+
.send()
|
|
129
|
+
.await?;
|
|
130
|
+
|
|
131
|
+
if !response.status().is_success() {
|
|
132
|
+
let status = response.status();
|
|
133
|
+
let body = response.text().await.unwrap_or_default();
|
|
134
|
+
anyhow::bail!("Brave search failed ({}): {}", status, body);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let data: serde_json::Value = response.json().await?;
|
|
138
|
+
let mut results = Vec::new();
|
|
139
|
+
|
|
140
|
+
if let Some(web) = data.get("web") {
|
|
141
|
+
if let Some(pages) = web.get("results").and_then(|r| r.as_array()) {
|
|
142
|
+
for page in pages.iter().take(self.max_results) {
|
|
143
|
+
results.push(SearchResult {
|
|
144
|
+
title: page
|
|
145
|
+
.get("title")
|
|
146
|
+
.and_then(|t| t.as_str())
|
|
147
|
+
.unwrap_or("")
|
|
148
|
+
.to_string(),
|
|
149
|
+
url: page
|
|
150
|
+
.get("url")
|
|
151
|
+
.and_then(|u| u.as_str())
|
|
152
|
+
.unwrap_or("")
|
|
153
|
+
.to_string(),
|
|
154
|
+
snippet: page
|
|
155
|
+
.get("description")
|
|
156
|
+
.and_then(|d| d.as_str())
|
|
157
|
+
.unwrap_or("")
|
|
158
|
+
.to_string(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
Ok(results)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[derive(Debug, Clone)]
|
|
169
|
+
struct SearchResult {
|
|
170
|
+
title: String,
|
|
171
|
+
url: String,
|
|
172
|
+
snippet: String,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[async_trait]
|
|
176
|
+
impl Tool for WebSearchTool {
|
|
177
|
+
fn name(&self) -> &str {
|
|
178
|
+
"web_search"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn description(&self) -> &str {
|
|
182
|
+
"Search the web using DuckDuckGo (free) or Brave (requires API key)"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fn parameters_schema(&self) -> serde_json::Value {
|
|
186
|
+
json!({
|
|
187
|
+
"type": "object",
|
|
188
|
+
"properties": {
|
|
189
|
+
"query": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "Search query"
|
|
192
|
+
},
|
|
193
|
+
"max_results": {
|
|
194
|
+
"type": "integer",
|
|
195
|
+
"description": "Maximum number of results (1-10)",
|
|
196
|
+
"minimum": 1,
|
|
197
|
+
"maximum": 10,
|
|
198
|
+
"default": 5
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"required": ["query"]
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fn requires_network(&self) -> bool {
|
|
206
|
+
true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
|
|
210
|
+
let query = args
|
|
211
|
+
.get("query")
|
|
212
|
+
.and_then(|v| v.as_str())
|
|
213
|
+
.ok_or_else(|| anyhow::anyhow!("Missing 'query' parameter"))?;
|
|
214
|
+
|
|
215
|
+
let max_results = args
|
|
216
|
+
.get("max_results")
|
|
217
|
+
.and_then(|v| v.as_u64())
|
|
218
|
+
.map(|n| n as usize)
|
|
219
|
+
.unwrap_or(DEFAULT_MAX_RESULTS)
|
|
220
|
+
.clamp(1, 10);
|
|
221
|
+
|
|
222
|
+
let results = match self.provider.as_str() {
|
|
223
|
+
"brave" => self.search_brave(query).await?,
|
|
224
|
+
"duckduckgo" | _ => self.search_duckduckgo(query).await?,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
let results_json: Vec<serde_json::Value> = results
|
|
228
|
+
.into_iter()
|
|
229
|
+
.take(max_results)
|
|
230
|
+
.map(|r| {
|
|
231
|
+
json!({
|
|
232
|
+
"title": r.title,
|
|
233
|
+
"url": r.url,
|
|
234
|
+
"snippet": r.snippet
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
.collect();
|
|
238
|
+
|
|
239
|
+
Ok(json!({
|
|
240
|
+
"success": true,
|
|
241
|
+
"query": query,
|
|
242
|
+
"provider": self.provider,
|
|
243
|
+
"results": results_json,
|
|
244
|
+
"count": results_json.len()
|
|
245
|
+
}))
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#[cfg(test)]
|
|
250
|
+
mod tests {
|
|
251
|
+
use super::*;
|
|
252
|
+
|
|
253
|
+
#[tokio::test]
|
|
254
|
+
async fn test_web_search_duckduckgo() {
|
|
255
|
+
let tool = WebSearchTool::new("duckduckgo");
|
|
256
|
+
let result = tool
|
|
257
|
+
.execute(json!({
|
|
258
|
+
"query": "Rust programming language",
|
|
259
|
+
"max_results": 3
|
|
260
|
+
}))
|
|
261
|
+
.await;
|
|
262
|
+
|
|
263
|
+
// May fail if no network, but should parse correctly
|
|
264
|
+
if let Ok(response) = result {
|
|
265
|
+
assert_eq!(response["success"], true);
|
|
266
|
+
assert!(response["count"].as_u64().unwrap_or(0) > 0);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#[test]
|
|
271
|
+
fn test_web_search_schema() {
|
|
272
|
+
let tool = WebSearchTool::new("duckduckgo");
|
|
273
|
+
let schema = tool.parameters_schema();
|
|
274
|
+
assert!(schema["properties"]["query"].is_object());
|
|
275
|
+
assert!(schema["properties"]["max_results"].is_object());
|
|
276
|
+
}
|
|
277
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
//! Air-Gapped E2E Tests - Verify zero network egress
|
|
2
|
+
//!
|
|
3
|
+
//! These tests verify that air-gapped mode:
|
|
4
|
+
//! 1. Blocks network-requiring providers
|
|
5
|
+
//! 2. Blocks network-requiring tools
|
|
6
|
+
//! 3. Produces NetworkViolation errors
|
|
7
|
+
//! 4. Allows local-only operations to proceed
|
|
8
|
+
//!
|
|
9
|
+
//! @see docs/feat-07-air-gapped-hardening.md
|
|
10
|
+
|
|
11
|
+
use enact_core::kernel::{
|
|
12
|
+
ExecutionKernel, ExecutionState, StepType, ViolationType,
|
|
13
|
+
ExecutionErrorCategory, TenantId,
|
|
14
|
+
};
|
|
15
|
+
use enact_core::context::TenantContext;
|
|
16
|
+
use enact_core::providers::{ModelProvider, ChatRequest, ChatResponse, ChatMessage, ModelCapabilities};
|
|
17
|
+
use enact_core::tool::Tool;
|
|
18
|
+
use async_trait::async_trait;
|
|
19
|
+
use serde_json::Value;
|
|
20
|
+
use std::sync::Arc;
|
|
21
|
+
|
|
22
|
+
/// Helper to create a test TenantContext
|
|
23
|
+
fn test_tenant() -> TenantContext {
|
|
24
|
+
TenantContext::new(TenantId::from("tenant_test"))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Mock Network-Requiring Provider
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
struct MockNetworkProvider;
|
|
32
|
+
|
|
33
|
+
#[async_trait]
|
|
34
|
+
impl ModelProvider for MockNetworkProvider {
|
|
35
|
+
fn name(&self) -> &str {
|
|
36
|
+
"mock-network-provider"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn requires_network(&self) -> bool {
|
|
40
|
+
true // This provider requires network
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn capabilities(&self) -> ModelCapabilities {
|
|
44
|
+
ModelCapabilities::default()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async fn chat(&self, _request: ChatRequest) -> anyhow::Result<ChatResponse> {
|
|
48
|
+
// This would normally make a network call
|
|
49
|
+
// In air-gapped mode, this should be blocked before reaching here
|
|
50
|
+
anyhow::bail!("Network call attempted (should be blocked in air-gapped mode)")
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Mock Local Provider
|
|
56
|
+
// =============================================================================
|
|
57
|
+
|
|
58
|
+
struct MockLocalProvider;
|
|
59
|
+
|
|
60
|
+
#[async_trait]
|
|
61
|
+
impl ModelProvider for MockLocalProvider {
|
|
62
|
+
fn name(&self) -> &str {
|
|
63
|
+
"mock-local-provider"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn requires_network(&self) -> bool {
|
|
67
|
+
false // This provider does not require network
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn capabilities(&self) -> ModelCapabilities {
|
|
71
|
+
ModelCapabilities::default()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async fn chat(&self, _request: ChatRequest) -> anyhow::Result<ChatResponse> {
|
|
75
|
+
// Local provider - no network needed
|
|
76
|
+
Ok(ChatResponse {
|
|
77
|
+
id: "local-response-id".to_string(),
|
|
78
|
+
choices: vec![enact_core::providers::ChatChoice {
|
|
79
|
+
index: 0,
|
|
80
|
+
message: ChatMessage::assistant("Local response"),
|
|
81
|
+
finish_reason: Some("stop".to_string()),
|
|
82
|
+
}],
|
|
83
|
+
usage: Some(enact_core::providers::ChatUsage {
|
|
84
|
+
prompt_tokens: 10,
|
|
85
|
+
completion_tokens: 5,
|
|
86
|
+
total_tokens: 15,
|
|
87
|
+
}),
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// Mock Network-Requiring Tool
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
struct MockNetworkTool;
|
|
97
|
+
|
|
98
|
+
#[async_trait]
|
|
99
|
+
impl Tool for MockNetworkTool {
|
|
100
|
+
fn name(&self) -> &str {
|
|
101
|
+
"mock_network_tool"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fn description(&self) -> &str {
|
|
105
|
+
"A tool that requires network access"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn requires_network(&self) -> bool {
|
|
109
|
+
true // This tool requires network
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async fn execute(&self, _args: Value) -> anyhow::Result<Value> {
|
|
113
|
+
// This would normally make a network call
|
|
114
|
+
anyhow::bail!("Network call attempted (should be blocked in air-gapped mode)")
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Mock Local Tool
|
|
120
|
+
// =============================================================================
|
|
121
|
+
|
|
122
|
+
struct MockLocalTool;
|
|
123
|
+
|
|
124
|
+
#[async_trait]
|
|
125
|
+
impl Tool for MockLocalTool {
|
|
126
|
+
fn name(&self) -> &str {
|
|
127
|
+
"mock_local_tool"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn description(&self) -> &str {
|
|
131
|
+
"A tool that does not require network access"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn requires_network(&self) -> bool {
|
|
135
|
+
false // This tool does not require network
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async fn execute(&self, _args: Value) -> anyhow::Result<Value> {
|
|
139
|
+
// Local tool - no network needed
|
|
140
|
+
Ok(serde_json::json!({"result": "local computation"}))
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// =============================================================================
|
|
145
|
+
// Test Cases
|
|
146
|
+
// =============================================================================
|
|
147
|
+
|
|
148
|
+
/// Test that network-requiring providers are detected and blocked
|
|
149
|
+
///
|
|
150
|
+
/// This test verifies that:
|
|
151
|
+
/// - Providers with `requires_network() == true` are identified
|
|
152
|
+
/// - Attempting to use them in air-gapped mode should raise NetworkViolation
|
|
153
|
+
#[tokio::test]
|
|
154
|
+
async fn test_airgapped_blocks_network_provider() {
|
|
155
|
+
let network_provider = Arc::new(MockNetworkProvider);
|
|
156
|
+
|
|
157
|
+
// Verify the provider declares it requires network
|
|
158
|
+
assert!(network_provider.requires_network(), "Provider should declare network requirement");
|
|
159
|
+
|
|
160
|
+
// In a real implementation, the enforcement middleware would check this
|
|
161
|
+
// and raise a NetworkViolation before the provider is called.
|
|
162
|
+
// For now, we verify the provider correctly declares its requirement.
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Test that network-requiring tools are detected and blocked
|
|
166
|
+
///
|
|
167
|
+
/// This test verifies that:
|
|
168
|
+
/// - Tools with `requires_network() == true` are identified
|
|
169
|
+
/// - Attempting to use them in air-gapped mode should raise NetworkViolation
|
|
170
|
+
#[tokio::test]
|
|
171
|
+
async fn test_airgapped_blocks_network_tool() {
|
|
172
|
+
let network_tool = Arc::new(MockNetworkTool);
|
|
173
|
+
|
|
174
|
+
// Verify the tool declares it requires network
|
|
175
|
+
assert!(network_tool.requires_network(), "Tool should declare network requirement");
|
|
176
|
+
|
|
177
|
+
// In a real implementation, the enforcement middleware would check this
|
|
178
|
+
// and raise a NetworkViolation before the tool is called.
|
|
179
|
+
// For now, we verify the tool correctly declares its requirement.
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Test that local providers are allowed in air-gapped mode
|
|
183
|
+
///
|
|
184
|
+
/// This test verifies that:
|
|
185
|
+
/// - Providers with `requires_network() == false` are allowed
|
|
186
|
+
/// - They can be used without raising NetworkViolation
|
|
187
|
+
#[tokio::test]
|
|
188
|
+
async fn test_airgapped_allows_local_provider() {
|
|
189
|
+
let local_provider = Arc::new(MockLocalProvider);
|
|
190
|
+
|
|
191
|
+
// Verify the provider declares it does not require network
|
|
192
|
+
assert!(!local_provider.requires_network(), "Provider should declare no network requirement");
|
|
193
|
+
|
|
194
|
+
// The provider should be usable in air-gapped mode
|
|
195
|
+
let request = ChatRequest {
|
|
196
|
+
messages: vec![ChatMessage::user("test")],
|
|
197
|
+
temperature: None,
|
|
198
|
+
max_tokens: None,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
let response = local_provider.chat(request).await;
|
|
202
|
+
assert!(response.is_ok(), "Local provider should work in air-gapped mode");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Test that local tools are allowed in air-gapped mode
|
|
206
|
+
///
|
|
207
|
+
/// This test verifies that:
|
|
208
|
+
/// - Tools with `requires_network() == false` are allowed
|
|
209
|
+
/// - They can be used without raising NetworkViolation
|
|
210
|
+
#[tokio::test]
|
|
211
|
+
async fn test_airgapped_allows_local_tool() {
|
|
212
|
+
let local_tool = Arc::new(MockLocalTool);
|
|
213
|
+
|
|
214
|
+
// Verify the tool declares it does not require network
|
|
215
|
+
assert!(!local_tool.requires_network(), "Tool should declare no network requirement");
|
|
216
|
+
|
|
217
|
+
// The tool should be usable in air-gapped mode
|
|
218
|
+
let result = local_tool.execute(serde_json::json!({})).await;
|
|
219
|
+
assert!(result.is_ok(), "Local tool should work in air-gapped mode");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Test that NetworkViolation error type is correctly formatted
|
|
223
|
+
///
|
|
224
|
+
/// This test verifies that:
|
|
225
|
+
/// - NetworkViolation maps to PolicyViolation category
|
|
226
|
+
/// - Error is non-retryable
|
|
227
|
+
/// - Error code is "network_violation"
|
|
228
|
+
#[tokio::test]
|
|
229
|
+
async fn test_network_violation_error_format() {
|
|
230
|
+
use enact_core::kernel::EnforcementViolation;
|
|
231
|
+
|
|
232
|
+
let violation = EnforcementViolation::new(ViolationType::NetworkViolation, 0, 0);
|
|
233
|
+
let error = violation.to_error();
|
|
234
|
+
|
|
235
|
+
// Verify error category
|
|
236
|
+
assert_eq!(error.category, ExecutionErrorCategory::PolicyViolation);
|
|
237
|
+
|
|
238
|
+
// Verify error code
|
|
239
|
+
assert_eq!(error.code, Some("network_violation".to_string()));
|
|
240
|
+
|
|
241
|
+
// Verify error is non-retryable (PolicyViolation is fatal)
|
|
242
|
+
assert!(!error.retry_policy.retryable);
|
|
243
|
+
assert!(error.category.is_fatal());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Test that execution kernel can be created in air-gapped context
|
|
247
|
+
///
|
|
248
|
+
/// This test verifies that:
|
|
249
|
+
/// - ExecutionKernel can be instantiated
|
|
250
|
+
/// - Basic operations work without network
|
|
251
|
+
#[tokio::test]
|
|
252
|
+
async fn test_airgapped_kernel_basic_operations() {
|
|
253
|
+
let mut kernel = ExecutionKernel::new(test_tenant());
|
|
254
|
+
|
|
255
|
+
// Start execution
|
|
256
|
+
kernel.start().unwrap();
|
|
257
|
+
assert_eq!(kernel.state(), ExecutionState::Running);
|
|
258
|
+
|
|
259
|
+
// Begin a step (local operation, no network needed)
|
|
260
|
+
let step_id = kernel.begin_step(
|
|
261
|
+
StepType::LlmNode,
|
|
262
|
+
"test_step",
|
|
263
|
+
None,
|
|
264
|
+
).unwrap();
|
|
265
|
+
|
|
266
|
+
// Complete the step
|
|
267
|
+
kernel.complete_step(step_id, Some("output".to_string()), 100).unwrap();
|
|
268
|
+
|
|
269
|
+
// Complete execution
|
|
270
|
+
kernel.complete(Some("final output".to_string())).unwrap();
|
|
271
|
+
assert_eq!(kernel.state(), ExecutionState::Completed);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// Test that NetworkViolation is properly serialized
|
|
275
|
+
///
|
|
276
|
+
/// This test verifies that:
|
|
277
|
+
/// - ViolationType::NetworkViolation serializes correctly
|
|
278
|
+
/// - Error messages are formatted correctly
|
|
279
|
+
#[tokio::test]
|
|
280
|
+
async fn test_network_violation_serialization() {
|
|
281
|
+
use enact_core::kernel::EnforcementViolation;
|
|
282
|
+
|
|
283
|
+
let violation = EnforcementViolation::new(ViolationType::NetworkViolation, 0, 0);
|
|
284
|
+
|
|
285
|
+
// Verify string representation
|
|
286
|
+
assert_eq!(format!("{}", ViolationType::NetworkViolation), "network_violation");
|
|
287
|
+
|
|
288
|
+
// Verify violation message format
|
|
289
|
+
assert!(violation.message.contains("network_violation"));
|
|
290
|
+
}
|
|
291
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
use enact_core::kernel::{ExecutionEventType, ExecutionId, StepId};
|
|
2
|
+
use enact_core::streaming::{EventEmitter, EventLog, StreamEvent};
|
|
3
|
+
use enact_core::{EventStore, InMemoryEventStore};
|
|
4
|
+
use std::sync::Arc;
|
|
5
|
+
use tokio::time::Duration;
|
|
6
|
+
|
|
7
|
+
#[tokio::test]
|
|
8
|
+
async fn test_agentic_loop_event_persistence() {
|
|
9
|
+
// 1. Setup - Create an EventStore and EventEmitter
|
|
10
|
+
// InMemoryEventStore implements enact_core::EventStore (which is the streaming one)
|
|
11
|
+
let event_store = Arc::new(InMemoryEventStore::new());
|
|
12
|
+
let event_log = Arc::new(EventLog::new(event_store.clone()));
|
|
13
|
+
let execution_id = ExecutionId::new();
|
|
14
|
+
|
|
15
|
+
let mut emitter = EventEmitter::with_persistence(event_log.clone(), execution_id.clone());
|
|
16
|
+
emitter.set_event_log(event_log, execution_id.clone());
|
|
17
|
+
|
|
18
|
+
// 2. Simulate Agentic Loop - Tool Execution
|
|
19
|
+
let step_id = StepId::new();
|
|
20
|
+
let tool_call_id = "call_12345";
|
|
21
|
+
let tool_name = "weather_tool";
|
|
22
|
+
|
|
23
|
+
// 2a. Tool Input Available (Should be mapped to ExecutionEventType::ToolCallStart)
|
|
24
|
+
let input_payload = serde_json::json!({ "city": "San Francisco" });
|
|
25
|
+
emitter.emit(StreamEvent::ToolInputAvailable {
|
|
26
|
+
tool_call_id: tool_call_id.to_string(),
|
|
27
|
+
tool_name: tool_name.to_string(),
|
|
28
|
+
input: input_payload.clone(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 2b. Tool Output Available (Should be mapped to ExecutionEventType::ToolCallEnd)
|
|
32
|
+
let output_payload = serde_json::json!({ "temp": 72, "conditions": "Sunny" });
|
|
33
|
+
emitter.emit(StreamEvent::ToolOutputAvailable {
|
|
34
|
+
tool_call_id: tool_call_id.to_string(),
|
|
35
|
+
output: output_payload.clone(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 3. Simulate Agentic Loop - Checkpoint
|
|
39
|
+
let checkpoint_id = "chk_12345";
|
|
40
|
+
emitter.emit(StreamEvent::CheckpointSaved {
|
|
41
|
+
execution_id: execution_id.as_str().to_string(),
|
|
42
|
+
step_id: Some(step_id.as_str().to_string()),
|
|
43
|
+
checkpoint_id: checkpoint_id.to_string(),
|
|
44
|
+
state_hash: "hash_abc123".to_string(),
|
|
45
|
+
timestamp: 1234567890,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 4. Simulate Agentic Loop - Goal Evaluation
|
|
49
|
+
emitter.emit(StreamEvent::GoalEvaluated {
|
|
50
|
+
execution_id: execution_id.as_str().to_string(),
|
|
51
|
+
step_id: Some(step_id.as_str().to_string()),
|
|
52
|
+
goal_id: "goal_primary".to_string(),
|
|
53
|
+
status: "met".to_string(),
|
|
54
|
+
score: Some(1.0),
|
|
55
|
+
reason: Some("Task completed successfully".to_string()),
|
|
56
|
+
timestamp: 1234567890,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Allow async persistence to complete
|
|
60
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
61
|
+
|
|
62
|
+
// 5. Verification - Check EventStore (requires EventStore trait in scope)
|
|
63
|
+
let events = event_store
|
|
64
|
+
.get_by_execution(&execution_id)
|
|
65
|
+
.await
|
|
66
|
+
.expect("Failed to load events");
|
|
67
|
+
|
|
68
|
+
// We expect 4 events
|
|
69
|
+
assert_eq!(
|
|
70
|
+
events.len(),
|
|
71
|
+
4,
|
|
72
|
+
"Expected 4 persisted events, found {}",
|
|
73
|
+
events.len()
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Verify Event 1: ToolCallStart
|
|
77
|
+
let event1 = &events[0].event;
|
|
78
|
+
assert!(
|
|
79
|
+
matches!(event1.event_type, ExecutionEventType::ToolCallStart),
|
|
80
|
+
"Event 1 should be ToolCallStart"
|
|
81
|
+
);
|
|
82
|
+
let p1 = event1.payload.as_ref().expect("Payload missing");
|
|
83
|
+
assert_eq!(p1["tool_call_id"], tool_call_id);
|
|
84
|
+
assert_eq!(p1["tool_name"], tool_name);
|
|
85
|
+
assert_eq!(p1["input"], input_payload);
|
|
86
|
+
|
|
87
|
+
// Verify Event 2: ToolCallEnd
|
|
88
|
+
let event2 = &events[1].event;
|
|
89
|
+
assert!(
|
|
90
|
+
matches!(event2.event_type, ExecutionEventType::ToolCallEnd),
|
|
91
|
+
"Event 2 should be ToolCallEnd"
|
|
92
|
+
);
|
|
93
|
+
let p2 = event2.payload.as_ref().expect("Payload missing");
|
|
94
|
+
assert_eq!(p2["tool_call_id"], tool_call_id);
|
|
95
|
+
assert_eq!(p2["output"], output_payload);
|
|
96
|
+
|
|
97
|
+
// Verify Event 3: CheckpointSaved
|
|
98
|
+
let event3 = &events[2].event;
|
|
99
|
+
assert!(
|
|
100
|
+
matches!(event3.event_type, ExecutionEventType::CheckpointSaved),
|
|
101
|
+
"Event 3 should be CheckpointSaved"
|
|
102
|
+
);
|
|
103
|
+
let p3 = event3.payload.as_ref().expect("Payload missing");
|
|
104
|
+
assert_eq!(p3["checkpoint_id"], checkpoint_id);
|
|
105
|
+
assert_eq!(p3["state_hash"], "hash_abc123");
|
|
106
|
+
|
|
107
|
+
// Verify Event 4: GoalEvaluated
|
|
108
|
+
let event4 = &events[3].event;
|
|
109
|
+
assert!(
|
|
110
|
+
matches!(event4.event_type, ExecutionEventType::GoalEvaluated),
|
|
111
|
+
"Event 4 should be GoalEvaluated"
|
|
112
|
+
);
|
|
113
|
+
let p4 = event4.payload.as_ref().expect("Payload missing");
|
|
114
|
+
assert_eq!(p4["goal_id"], "goal_primary");
|
|
115
|
+
assert_eq!(p4["status"], "met");
|
|
116
|
+
assert_eq!(p4["score"], 1.0);
|
|
117
|
+
|
|
118
|
+
println!("✅ All agentic loop events persisted correctly!");
|
|
119
|
+
}
|