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,227 @@
|
|
|
1
|
+
//! Security policy for tool execution
|
|
2
|
+
//!
|
|
3
|
+
//! Provides autonomy levels, rate limiting, and path sandboxing.
|
|
4
|
+
|
|
5
|
+
use std::path::{Path, PathBuf};
|
|
6
|
+
use std::sync::atomic::{AtomicU32, Ordering};
|
|
7
|
+
|
|
8
|
+
/// Autonomy level for agent actions
|
|
9
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
10
|
+
pub enum AutonomyLevel {
|
|
11
|
+
/// Read-only access, no modifications allowed
|
|
12
|
+
ReadOnly,
|
|
13
|
+
/// Supervised mode (default) - some actions require approval
|
|
14
|
+
#[default]
|
|
15
|
+
Supervised,
|
|
16
|
+
/// Full autonomy - all actions allowed
|
|
17
|
+
Full,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Security policy configuration for tools
|
|
21
|
+
pub struct SecurityPolicy {
|
|
22
|
+
/// Autonomy level
|
|
23
|
+
pub autonomy: AutonomyLevel,
|
|
24
|
+
/// Workspace directory (tools are sandboxed to this)
|
|
25
|
+
pub workspace_dir: PathBuf,
|
|
26
|
+
/// Allowed shell commands (empty = allow all safe commands)
|
|
27
|
+
pub allowed_commands: Vec<String>,
|
|
28
|
+
/// Blocked shell commands
|
|
29
|
+
pub blocked_commands: Vec<String>,
|
|
30
|
+
/// Maximum actions per hour (0 = unlimited)
|
|
31
|
+
pub max_actions_per_hour: u32,
|
|
32
|
+
/// Action counter for rate limiting
|
|
33
|
+
action_count: AtomicU32,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl Default for SecurityPolicy {
|
|
37
|
+
fn default() -> Self {
|
|
38
|
+
Self {
|
|
39
|
+
autonomy: AutonomyLevel::Supervised,
|
|
40
|
+
workspace_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
|
41
|
+
allowed_commands: Vec::new(),
|
|
42
|
+
blocked_commands: vec![
|
|
43
|
+
"rm -rf /".into(),
|
|
44
|
+
"sudo".into(),
|
|
45
|
+
"chmod 777".into(),
|
|
46
|
+
"dd if=".into(),
|
|
47
|
+
"> /dev/".into(),
|
|
48
|
+
],
|
|
49
|
+
max_actions_per_hour: 1000,
|
|
50
|
+
action_count: AtomicU32::new(0),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl SecurityPolicy {
|
|
56
|
+
/// Create a new security policy
|
|
57
|
+
pub fn new(workspace_dir: PathBuf) -> Self {
|
|
58
|
+
Self {
|
|
59
|
+
workspace_dir,
|
|
60
|
+
..Self::default()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Check if the agent can perform write actions
|
|
65
|
+
pub fn can_act(&self) -> bool {
|
|
66
|
+
!matches!(self.autonomy, AutonomyLevel::ReadOnly)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Check if rate limited
|
|
70
|
+
pub fn is_rate_limited(&self) -> bool {
|
|
71
|
+
if self.max_actions_per_hour == 0 {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
self.action_count.load(Ordering::Relaxed) >= self.max_actions_per_hour
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Record an action and return true if allowed
|
|
78
|
+
pub fn record_action(&self) -> bool {
|
|
79
|
+
if self.max_actions_per_hour == 0 {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
let current = self.action_count.fetch_add(1, Ordering::Relaxed);
|
|
83
|
+
current < self.max_actions_per_hour
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Check if a relative path is allowed (no path traversal)
|
|
87
|
+
pub fn is_path_allowed(&self, path: &str) -> bool {
|
|
88
|
+
// Block absolute paths
|
|
89
|
+
if path.starts_with('/') || path.starts_with('\\') {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
// Block path traversal
|
|
93
|
+
if path.contains("..") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
// Block null bytes
|
|
97
|
+
if path.contains('\0') {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Check if a resolved (canonicalized) path is within workspace
|
|
104
|
+
pub fn is_resolved_path_allowed(&self, resolved: &Path) -> bool {
|
|
105
|
+
resolved.starts_with(&self.workspace_dir)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Validate a shell command for execution
|
|
109
|
+
pub fn validate_command_execution(&self, command: &str, approved: bool) -> Result<(), String> {
|
|
110
|
+
if !self.can_act() {
|
|
111
|
+
return Err("Shell command not allowed: autonomy is read-only".into());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check blocked commands
|
|
115
|
+
for blocked in &self.blocked_commands {
|
|
116
|
+
if command.contains(blocked) {
|
|
117
|
+
return Err(format!("Command blocked by security policy: {blocked}"));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// High-risk command patterns
|
|
122
|
+
let high_risk = [
|
|
123
|
+
"rm -rf",
|
|
124
|
+
"mkfs",
|
|
125
|
+
"dd if=",
|
|
126
|
+
"> /dev/",
|
|
127
|
+
"sudo",
|
|
128
|
+
"chmod 777",
|
|
129
|
+
"curl | sh",
|
|
130
|
+
"wget | sh",
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
for pattern in high_risk {
|
|
134
|
+
if command.contains(pattern) {
|
|
135
|
+
return Err(format!(
|
|
136
|
+
"high-risk command not allowed without explicit override: {pattern}"
|
|
137
|
+
));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Medium-risk commands need approval in supervised mode
|
|
142
|
+
if self.autonomy == AutonomyLevel::Supervised && !approved {
|
|
143
|
+
let medium_risk = ["rm", "mv", "cp", "touch", "mkdir", "chmod", "chown"];
|
|
144
|
+
for pattern in medium_risk {
|
|
145
|
+
if command.split_whitespace().next() == Some(pattern) {
|
|
146
|
+
return Err(format!(
|
|
147
|
+
"Command '{pattern}' requires explicit approval in supervised mode. Set approved=true."
|
|
148
|
+
));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// If allowed_commands is set, check against it
|
|
154
|
+
if !self.allowed_commands.is_empty() {
|
|
155
|
+
let cmd_name = command.split_whitespace().next().unwrap_or("");
|
|
156
|
+
if !self.allowed_commands.iter().any(|c| c == cmd_name) {
|
|
157
|
+
return Err(format!("Command '{cmd_name}' not in allowed commands list"));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
Ok(())
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#[cfg(test)]
|
|
166
|
+
mod tests {
|
|
167
|
+
use super::*;
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn default_is_supervised() {
|
|
171
|
+
let policy = SecurityPolicy::default();
|
|
172
|
+
assert_eq!(policy.autonomy, AutonomyLevel::Supervised);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn can_act_respects_autonomy() {
|
|
177
|
+
let mut policy = SecurityPolicy::default();
|
|
178
|
+
assert!(policy.can_act());
|
|
179
|
+
|
|
180
|
+
policy.autonomy = AutonomyLevel::ReadOnly;
|
|
181
|
+
assert!(!policy.can_act());
|
|
182
|
+
|
|
183
|
+
policy.autonomy = AutonomyLevel::Full;
|
|
184
|
+
assert!(policy.can_act());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[test]
|
|
188
|
+
fn path_blocks_traversal() {
|
|
189
|
+
let policy = SecurityPolicy::default();
|
|
190
|
+
assert!(!policy.is_path_allowed("../etc/passwd"));
|
|
191
|
+
assert!(!policy.is_path_allowed("/etc/passwd"));
|
|
192
|
+
assert!(policy.is_path_allowed("src/main.rs"));
|
|
193
|
+
assert!(policy.is_path_allowed("file.txt"));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn blocks_high_risk_commands() {
|
|
198
|
+
let policy = SecurityPolicy::default();
|
|
199
|
+
assert!(policy.validate_command_execution("rm -rf /", false).is_err());
|
|
200
|
+
assert!(policy.validate_command_execution("sudo apt install", false).is_err());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#[test]
|
|
204
|
+
fn medium_risk_needs_approval() {
|
|
205
|
+
let policy = SecurityPolicy::default();
|
|
206
|
+
let result = policy.validate_command_execution("rm file.txt", false);
|
|
207
|
+
assert!(result.is_err());
|
|
208
|
+
assert!(result.unwrap_err().contains("approval"));
|
|
209
|
+
|
|
210
|
+
let result = policy.validate_command_execution("rm file.txt", true);
|
|
211
|
+
assert!(result.is_ok());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#[test]
|
|
215
|
+
fn rate_limiting_works() {
|
|
216
|
+
let policy = SecurityPolicy {
|
|
217
|
+
max_actions_per_hour: 2,
|
|
218
|
+
..SecurityPolicy::default()
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
assert!(!policy.is_rate_limited());
|
|
222
|
+
assert!(policy.record_action());
|
|
223
|
+
assert!(policy.record_action());
|
|
224
|
+
assert!(!policy.record_action());
|
|
225
|
+
assert!(policy.is_rate_limited());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
//! Shell command execution tool with sandboxing
|
|
2
|
+
|
|
3
|
+
use crate::security::SecurityPolicy;
|
|
4
|
+
use crate::traits::{Tool, ToolResult};
|
|
5
|
+
use async_trait::async_trait;
|
|
6
|
+
use serde_json::json;
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
use std::time::Duration;
|
|
9
|
+
|
|
10
|
+
/// Maximum shell command execution time before kill.
|
|
11
|
+
const SHELL_TIMEOUT_SECS: u64 = 60;
|
|
12
|
+
/// Maximum output size in bytes (1MB).
|
|
13
|
+
const MAX_OUTPUT_BYTES: usize = 1_048_576;
|
|
14
|
+
/// Environment variables safe to pass to shell commands.
|
|
15
|
+
const SAFE_ENV_VARS: &[&str] = &[
|
|
16
|
+
"PATH", "HOME", "TERM", "LANG", "LC_ALL", "LC_CTYPE", "USER", "SHELL", "TMPDIR",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/// Shell command execution tool with sandboxing
|
|
20
|
+
pub struct ShellTool {
|
|
21
|
+
security: Arc<SecurityPolicy>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl ShellTool {
|
|
25
|
+
pub fn new(security: Arc<SecurityPolicy>) -> Self {
|
|
26
|
+
Self { security }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[async_trait]
|
|
31
|
+
impl Tool for ShellTool {
|
|
32
|
+
fn name(&self) -> &str {
|
|
33
|
+
"shell"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fn description(&self) -> &str {
|
|
37
|
+
"Execute a shell command in the workspace directory"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn parameters_schema(&self) -> serde_json::Value {
|
|
41
|
+
json!({
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"command": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "The shell command to execute"
|
|
47
|
+
},
|
|
48
|
+
"approved": {
|
|
49
|
+
"type": "boolean",
|
|
50
|
+
"description": "Set true to explicitly approve medium/high-risk commands in supervised mode",
|
|
51
|
+
"default": false
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"required": ["command"]
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
|
|
59
|
+
let command = args
|
|
60
|
+
.get("command")
|
|
61
|
+
.and_then(|v| v.as_str())
|
|
62
|
+
.ok_or_else(|| anyhow::anyhow!("Missing 'command' parameter"))?;
|
|
63
|
+
let approved = args
|
|
64
|
+
.get("approved")
|
|
65
|
+
.and_then(|v| v.as_bool())
|
|
66
|
+
.unwrap_or(false);
|
|
67
|
+
|
|
68
|
+
if self.security.is_rate_limited() {
|
|
69
|
+
return Ok(ToolResult::failure(
|
|
70
|
+
"Rate limit exceeded: too many actions in the last hour",
|
|
71
|
+
));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if let Err(reason) = self.security.validate_command_execution(command, approved) {
|
|
75
|
+
return Ok(ToolResult::failure(reason));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if !self.security.record_action() {
|
|
79
|
+
return Ok(ToolResult::failure(
|
|
80
|
+
"Rate limit exceeded: action budget exhausted",
|
|
81
|
+
));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Build command with cleaned environment
|
|
85
|
+
let mut cmd = tokio::process::Command::new("sh");
|
|
86
|
+
cmd.arg("-c")
|
|
87
|
+
.arg(command)
|
|
88
|
+
.current_dir(&self.security.workspace_dir)
|
|
89
|
+
.env_clear();
|
|
90
|
+
|
|
91
|
+
for var in SAFE_ENV_VARS {
|
|
92
|
+
if let Ok(val) = std::env::var(var) {
|
|
93
|
+
cmd.env(var, val);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let result =
|
|
98
|
+
tokio::time::timeout(Duration::from_secs(SHELL_TIMEOUT_SECS), cmd.output()).await;
|
|
99
|
+
|
|
100
|
+
match result {
|
|
101
|
+
Ok(Ok(output)) => {
|
|
102
|
+
let mut stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
103
|
+
let mut stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
104
|
+
|
|
105
|
+
// Truncate output to prevent OOM
|
|
106
|
+
if stdout.len() > MAX_OUTPUT_BYTES {
|
|
107
|
+
stdout.truncate(stdout.floor_char_boundary(MAX_OUTPUT_BYTES));
|
|
108
|
+
stdout.push_str("\n... [output truncated at 1MB]");
|
|
109
|
+
}
|
|
110
|
+
if stderr.len() > MAX_OUTPUT_BYTES {
|
|
111
|
+
stderr.truncate(stderr.floor_char_boundary(MAX_OUTPUT_BYTES));
|
|
112
|
+
stderr.push_str("\n... [stderr truncated at 1MB]");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Ok(ToolResult {
|
|
116
|
+
success: output.status.success(),
|
|
117
|
+
output: stdout,
|
|
118
|
+
error: if stderr.is_empty() {
|
|
119
|
+
None
|
|
120
|
+
} else {
|
|
121
|
+
Some(stderr)
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
Ok(Err(e)) => Ok(ToolResult::failure(format!(
|
|
126
|
+
"Failed to execute command: {e}"
|
|
127
|
+
))),
|
|
128
|
+
Err(_) => Ok(ToolResult::failure(format!(
|
|
129
|
+
"Command timed out after {SHELL_TIMEOUT_SECS}s and was killed"
|
|
130
|
+
))),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[cfg(test)]
|
|
136
|
+
mod tests {
|
|
137
|
+
use super::*;
|
|
138
|
+
use crate::security::AutonomyLevel;
|
|
139
|
+
|
|
140
|
+
fn test_security(autonomy: AutonomyLevel) -> Arc<SecurityPolicy> {
|
|
141
|
+
Arc::new(SecurityPolicy {
|
|
142
|
+
autonomy,
|
|
143
|
+
workspace_dir: std::env::temp_dir(),
|
|
144
|
+
..SecurityPolicy::default()
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn shell_tool_name() {
|
|
150
|
+
let tool = ShellTool::new(test_security(AutonomyLevel::Supervised));
|
|
151
|
+
assert_eq!(tool.name(), "shell");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#[test]
|
|
155
|
+
fn shell_tool_schema_has_command() {
|
|
156
|
+
let tool = ShellTool::new(test_security(AutonomyLevel::Supervised));
|
|
157
|
+
let schema = tool.parameters_schema();
|
|
158
|
+
assert!(schema["properties"]["command"].is_object());
|
|
159
|
+
assert!(schema["required"]
|
|
160
|
+
.as_array()
|
|
161
|
+
.unwrap()
|
|
162
|
+
.contains(&json!("command")));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#[tokio::test]
|
|
166
|
+
async fn shell_executes_allowed_command() {
|
|
167
|
+
let tool = ShellTool::new(test_security(AutonomyLevel::Supervised));
|
|
168
|
+
let result = tool
|
|
169
|
+
.execute(json!({"command": "echo hello"}))
|
|
170
|
+
.await
|
|
171
|
+
.unwrap();
|
|
172
|
+
assert!(result.success);
|
|
173
|
+
assert!(result.output.trim().contains("hello"));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#[tokio::test]
|
|
177
|
+
async fn shell_blocks_disallowed_command() {
|
|
178
|
+
let tool = ShellTool::new(test_security(AutonomyLevel::Supervised));
|
|
179
|
+
let result = tool.execute(json!({"command": "rm -rf /"})).await.unwrap();
|
|
180
|
+
assert!(!result.success);
|
|
181
|
+
assert!(result.error.is_some());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#[tokio::test]
|
|
185
|
+
async fn shell_blocks_readonly() {
|
|
186
|
+
let tool = ShellTool::new(test_security(AutonomyLevel::ReadOnly));
|
|
187
|
+
let result = tool.execute(json!({"command": "ls"})).await.unwrap();
|
|
188
|
+
assert!(!result.success);
|
|
189
|
+
assert!(result.error.as_ref().unwrap().contains("read-only"));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
//! Core tool traits and types
|
|
2
|
+
|
|
3
|
+
use async_trait::async_trait;
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
|
|
6
|
+
/// Result of a tool execution
|
|
7
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
8
|
+
pub struct ToolResult {
|
|
9
|
+
pub success: bool,
|
|
10
|
+
pub output: String,
|
|
11
|
+
pub error: Option<String>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl ToolResult {
|
|
15
|
+
/// Create a successful result
|
|
16
|
+
pub fn success(output: impl Into<String>) -> Self {
|
|
17
|
+
Self {
|
|
18
|
+
success: true,
|
|
19
|
+
output: output.into(),
|
|
20
|
+
error: None,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Create a failure result
|
|
25
|
+
pub fn failure(error: impl Into<String>) -> Self {
|
|
26
|
+
Self {
|
|
27
|
+
success: false,
|
|
28
|
+
output: String::new(),
|
|
29
|
+
error: Some(error.into()),
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Description of a tool for the LLM
|
|
35
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
36
|
+
pub struct ToolSpec {
|
|
37
|
+
pub name: String,
|
|
38
|
+
pub description: String,
|
|
39
|
+
pub parameters: serde_json::Value,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Core tool trait — implement for any capability
|
|
43
|
+
#[async_trait]
|
|
44
|
+
pub trait Tool: Send + Sync {
|
|
45
|
+
/// Tool name (used in LLM function calling)
|
|
46
|
+
fn name(&self) -> &str;
|
|
47
|
+
|
|
48
|
+
/// Human-readable description
|
|
49
|
+
fn description(&self) -> &str;
|
|
50
|
+
|
|
51
|
+
/// JSON schema for parameters
|
|
52
|
+
fn parameters_schema(&self) -> serde_json::Value;
|
|
53
|
+
|
|
54
|
+
/// Execute the tool with given arguments
|
|
55
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult>;
|
|
56
|
+
|
|
57
|
+
/// Get the full spec for LLM registration
|
|
58
|
+
fn spec(&self) -> ToolSpec {
|
|
59
|
+
ToolSpec {
|
|
60
|
+
name: self.name().to_string(),
|
|
61
|
+
description: self.description().to_string(),
|
|
62
|
+
parameters: self.parameters_schema(),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[cfg(test)]
|
|
68
|
+
mod tests {
|
|
69
|
+
use super::*;
|
|
70
|
+
|
|
71
|
+
struct DummyTool;
|
|
72
|
+
|
|
73
|
+
#[async_trait]
|
|
74
|
+
impl Tool for DummyTool {
|
|
75
|
+
fn name(&self) -> &str {
|
|
76
|
+
"dummy_tool"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn description(&self) -> &str {
|
|
80
|
+
"A deterministic test tool"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn parameters_schema(&self) -> serde_json::Value {
|
|
84
|
+
serde_json::json!({
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"value": { "type": "string" }
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
|
|
93
|
+
Ok(ToolResult {
|
|
94
|
+
success: true,
|
|
95
|
+
output: args
|
|
96
|
+
.get("value")
|
|
97
|
+
.and_then(serde_json::Value::as_str)
|
|
98
|
+
.unwrap_or_default()
|
|
99
|
+
.to_string(),
|
|
100
|
+
error: None,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[test]
|
|
106
|
+
fn spec_uses_tool_metadata_and_schema() {
|
|
107
|
+
let tool = DummyTool;
|
|
108
|
+
let spec = tool.spec();
|
|
109
|
+
|
|
110
|
+
assert_eq!(spec.name, "dummy_tool");
|
|
111
|
+
assert_eq!(spec.description, "A deterministic test tool");
|
|
112
|
+
assert_eq!(spec.parameters["type"], "object");
|
|
113
|
+
assert_eq!(spec.parameters["properties"]["value"]["type"], "string");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#[tokio::test]
|
|
117
|
+
async fn execute_returns_expected_output() {
|
|
118
|
+
let tool = DummyTool;
|
|
119
|
+
let result = tool
|
|
120
|
+
.execute(serde_json::json!({ "value": "hello-tool" }))
|
|
121
|
+
.await
|
|
122
|
+
.unwrap();
|
|
123
|
+
|
|
124
|
+
assert!(result.success);
|
|
125
|
+
assert_eq!(result.output, "hello-tool");
|
|
126
|
+
assert!(result.error.is_none());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn tool_result_success_helper() {
|
|
131
|
+
let result = ToolResult::success("done");
|
|
132
|
+
assert!(result.success);
|
|
133
|
+
assert_eq!(result.output, "done");
|
|
134
|
+
assert!(result.error.is_none());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[test]
|
|
138
|
+
fn tool_result_failure_helper() {
|
|
139
|
+
let result = ToolResult::failure("boom");
|
|
140
|
+
assert!(!result.success);
|
|
141
|
+
assert_eq!(result.output, "");
|
|
142
|
+
assert_eq!(result.error.as_deref(), Some("boom"));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[test]
|
|
146
|
+
fn tool_result_serialization_roundtrip() {
|
|
147
|
+
let result = ToolResult {
|
|
148
|
+
success: false,
|
|
149
|
+
output: String::new(),
|
|
150
|
+
error: Some("boom".into()),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
let json = serde_json::to_string(&result).unwrap();
|
|
154
|
+
let parsed: ToolResult = serde_json::from_str(&json).unwrap();
|
|
155
|
+
|
|
156
|
+
assert!(!parsed.success);
|
|
157
|
+
assert_eq!(parsed.error.as_deref(), Some("boom"));
|
|
158
|
+
}
|
|
159
|
+
}
|
package/docs/Makefile
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Enact Documentation - Makefile
|
|
2
|
+
# Built with Zola (https://www.getzola.org/)
|
|
3
|
+
|
|
4
|
+
.PHONY: build serve clean check install-zola help
|
|
5
|
+
|
|
6
|
+
# Default target
|
|
7
|
+
help:
|
|
8
|
+
@echo "Enact Documentation Commands"
|
|
9
|
+
@echo "============================"
|
|
10
|
+
@echo ""
|
|
11
|
+
@echo " make build Build the documentation site"
|
|
12
|
+
@echo " make serve Start development server with hot reload"
|
|
13
|
+
@echo " make clean Remove build artifacts"
|
|
14
|
+
@echo " make check Check for broken links and errors"
|
|
15
|
+
@echo " make install-zola Install Zola static site generator"
|
|
16
|
+
@echo ""
|
|
17
|
+
@echo "Development server runs at http://127.0.0.1:1111"
|
|
18
|
+
|
|
19
|
+
# Build the documentation site
|
|
20
|
+
build:
|
|
21
|
+
@echo "Building documentation..."
|
|
22
|
+
zola build
|
|
23
|
+
@echo "Build complete! Output in public/"
|
|
24
|
+
|
|
25
|
+
# Start development server with hot reload
|
|
26
|
+
serve:
|
|
27
|
+
@echo "Starting development server at http://127.0.0.1:1111"
|
|
28
|
+
zola serve
|
|
29
|
+
|
|
30
|
+
# Serve on all interfaces (useful for testing on other devices)
|
|
31
|
+
serve-public:
|
|
32
|
+
@echo "Starting public server at http://0.0.0.0:1111"
|
|
33
|
+
zola serve --interface 0.0.0.0
|
|
34
|
+
|
|
35
|
+
# Remove build artifacts
|
|
36
|
+
clean:
|
|
37
|
+
@echo "Cleaning build artifacts..."
|
|
38
|
+
rm -rf public/
|
|
39
|
+
@echo "Clean complete!"
|
|
40
|
+
|
|
41
|
+
# Check for errors without building
|
|
42
|
+
check:
|
|
43
|
+
@echo "Checking documentation..."
|
|
44
|
+
zola check
|
|
45
|
+
@echo "Check complete!"
|
|
46
|
+
|
|
47
|
+
# Install Zola (macOS)
|
|
48
|
+
install-zola:
|
|
49
|
+
@echo "Installing Zola..."
|
|
50
|
+
@if command -v brew >/dev/null 2>&1; then \
|
|
51
|
+
brew install zola; \
|
|
52
|
+
elif command -v cargo >/dev/null 2>&1; then \
|
|
53
|
+
cargo install zola; \
|
|
54
|
+
else \
|
|
55
|
+
echo "Please install Homebrew or Cargo first"; \
|
|
56
|
+
exit 1; \
|
|
57
|
+
fi
|
|
58
|
+
@echo "Zola installed!"
|
|
59
|
+
|
|
60
|
+
# Build for production (minified)
|
|
61
|
+
build-prod:
|
|
62
|
+
@echo "Building for production..."
|
|
63
|
+
zola build --force
|
|
64
|
+
@echo "Production build complete!"
|
|
65
|
+
|
|
66
|
+
# Watch for changes and rebuild (alternative to serve)
|
|
67
|
+
watch:
|
|
68
|
+
@echo "Watching for changes..."
|
|
69
|
+
zola build --drafts
|
|
70
|
+
@while true; do \
|
|
71
|
+
fswatch -1 content/ templates/ sass/ static/ config.toml && \
|
|
72
|
+
echo "Rebuilding..." && \
|
|
73
|
+
zola build --drafts; \
|
|
74
|
+
done
|