rubino-agent 0.3.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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +115 -0
- data/.rubocop_todo.yml +955 -0
- data/.ruby-version +1 -0
- data/AGENTS.md +97 -0
- data/CHANGELOG.md +344 -0
- data/CONTRIBUTING.md +69 -0
- data/LICENSE +21 -0
- data/README.md +200 -0
- data/Rakefile +8 -0
- data/docs/agents.md +190 -0
- data/docs/api/v1.md +414 -0
- data/docs/architecture.md +177 -0
- data/docs/commands.md +375 -0
- data/docs/configuration.md +590 -0
- data/docs/getting-started.md +143 -0
- data/docs/jobs.md +332 -0
- data/docs/mcp.md +128 -0
- data/docs/memory.md +98 -0
- data/docs/models-and-keys.md +173 -0
- data/docs/oauth-providers.md +145 -0
- data/docs/plugins.md +195 -0
- data/docs/security.md +145 -0
- data/docs/skills.md +322 -0
- data/docs/tools.md +395 -0
- data/docs/troubleshooting.md +73 -0
- data/exe/rubino +9 -0
- data/install.sh +275 -0
- data/lib/rubino/active_skill.rb +50 -0
- data/lib/rubino/agent/agent_registry.rb +120 -0
- data/lib/rubino/agent/backoff_policy.rb +116 -0
- data/lib/rubino/agent/definition.rb +128 -0
- data/lib/rubino/agent/degenerate_recovery.rb +271 -0
- data/lib/rubino/agent/fallback_chain.rb +194 -0
- data/lib/rubino/agent/iteration_budget.rb +50 -0
- data/lib/rubino/agent/loop.rb +617 -0
- data/lib/rubino/agent/model_call_runner.rb +383 -0
- data/lib/rubino/agent/prompts/build.txt +69 -0
- data/lib/rubino/agent/prompts/compaction.txt +20 -0
- data/lib/rubino/agent/prompts/explore.txt +19 -0
- data/lib/rubino/agent/prompts/general.txt +20 -0
- data/lib/rubino/agent/prompts/plan.txt +31 -0
- data/lib/rubino/agent/response_validator.rb +70 -0
- data/lib/rubino/agent/router.rb +65 -0
- data/lib/rubino/agent/runner.rb +195 -0
- data/lib/rubino/agent/tool_executor.rb +402 -0
- data/lib/rubino/agent/truncation_continuation.rb +137 -0
- data/lib/rubino/api/middleware/auth.rb +43 -0
- data/lib/rubino/api/middleware/error_handler.rb +65 -0
- data/lib/rubino/api/middleware/json_parser.rb +100 -0
- data/lib/rubino/api/middleware/observability.rb +59 -0
- data/lib/rubino/api/middleware/rate_limit.rb +136 -0
- data/lib/rubino/api/operations/approvals/decide_operation.rb +49 -0
- data/lib/rubino/api/operations/clarifications/decide_operation.rb +44 -0
- data/lib/rubino/api/operations/cron_jobs/create_operation.rb +46 -0
- data/lib/rubino/api/operations/cron_jobs/delete_operation.rb +36 -0
- data/lib/rubino/api/operations/cron_jobs/list_operation.rb +55 -0
- data/lib/rubino/api/operations/cron_jobs/pause_operation.rb +34 -0
- data/lib/rubino/api/operations/cron_jobs/resume_operation.rb +34 -0
- data/lib/rubino/api/operations/cron_jobs/schedule_validation.rb +30 -0
- data/lib/rubino/api/operations/cron_jobs/show_operation.rb +32 -0
- data/lib/rubino/api/operations/cron_jobs/trigger_operation.rb +38 -0
- data/lib/rubino/api/operations/cron_jobs/update_operation.rb +42 -0
- data/lib/rubino/api/operations/files/read_operation.rb +40 -0
- data/lib/rubino/api/operations/files/upload_operation.rb +175 -0
- data/lib/rubino/api/operations/health_operation.rb +46 -0
- data/lib/rubino/api/operations/memory/delete_operation.rb +32 -0
- data/lib/rubino/api/operations/memory/index_operation.rb +80 -0
- data/lib/rubino/api/operations/memory/stats_operation.rb +28 -0
- data/lib/rubino/api/operations/metrics_operation.rb +18 -0
- data/lib/rubino/api/operations/mode/show_operation.rb +29 -0
- data/lib/rubino/api/operations/mode/update_operation.rb +42 -0
- data/lib/rubino/api/operations/models/list_operation.rb +45 -0
- data/lib/rubino/api/operations/oauth/connections/disconnect_operation.rb +77 -0
- data/lib/rubino/api/operations/oauth/connections/list_operation.rb +36 -0
- data/lib/rubino/api/operations/oauth/providers/callback_operation.rb +82 -0
- data/lib/rubino/api/operations/oauth/providers/connect_operation.rb +44 -0
- data/lib/rubino/api/operations/oauth/providers/list_operation.rb +35 -0
- data/lib/rubino/api/operations/oauth/serializer.rb +21 -0
- data/lib/rubino/api/operations/runs/create_operation.rb +77 -0
- data/lib/rubino/api/operations/runs/events_operation.rb +195 -0
- data/lib/rubino/api/operations/runs/stop_operation.rb +34 -0
- data/lib/rubino/api/operations/sessions/create_operation.rb +46 -0
- data/lib/rubino/api/operations/sessions/delete_operation.rb +33 -0
- data/lib/rubino/api/operations/sessions/index_operation.rb +82 -0
- data/lib/rubino/api/operations/sessions/retry_operation.rb +45 -0
- data/lib/rubino/api/operations/sessions/show_operation.rb +59 -0
- data/lib/rubino/api/operations/sessions/undo_operation.rb +38 -0
- data/lib/rubino/api/operations/skills/list_operation.rb +34 -0
- data/lib/rubino/api/operations/skills/toggle_operation.rb +40 -0
- data/lib/rubino/api/operations/tasks/index_operation.rb +30 -0
- data/lib/rubino/api/operations/tasks/serializer.rb +60 -0
- data/lib/rubino/api/operations/tasks/show_operation.rb +33 -0
- data/lib/rubino/api/operations/tasks/stop_operation.rb +47 -0
- data/lib/rubino/api/request.rb +54 -0
- data/lib/rubino/api/responses.rb +64 -0
- data/lib/rubino/api/router.rb +72 -0
- data/lib/rubino/api/schemas.rb +103 -0
- data/lib/rubino/api/server.rb +102 -0
- data/lib/rubino/api/tls.rb +108 -0
- data/lib/rubino/attachments/classification.rb +16 -0
- data/lib/rubino/attachments/classify.rb +171 -0
- data/lib/rubino/attachments/defang.rb +47 -0
- data/lib/rubino/attachments/policy.rb +36 -0
- data/lib/rubino/attachments/preamble.rb +120 -0
- data/lib/rubino/boot/encryption_key.rb +32 -0
- data/lib/rubino/cli/chat/bang_shell.rb +257 -0
- data/lib/rubino/cli/chat/completion_builder.rb +290 -0
- data/lib/rubino/cli/chat/idle_card_host.rb +69 -0
- data/lib/rubino/cli/chat/image_inbox.rb +168 -0
- data/lib/rubino/cli/chat/session_resolver.rb +176 -0
- data/lib/rubino/cli/chat_command.rb +1674 -0
- data/lib/rubino/cli/commands.rb +250 -0
- data/lib/rubino/cli/config_command.rb +96 -0
- data/lib/rubino/cli/doctor_command.rb +251 -0
- data/lib/rubino/cli/jobs_command.rb +60 -0
- data/lib/rubino/cli/memory_command.rb +135 -0
- data/lib/rubino/cli/onboarding_wizard.rb +207 -0
- data/lib/rubino/cli/server_command.rb +139 -0
- data/lib/rubino/cli/session_command.rb +125 -0
- data/lib/rubino/cli/setup_command.rb +107 -0
- data/lib/rubino/cli/skills_command.rb +85 -0
- data/lib/rubino/cli/tools_command.rb +81 -0
- data/lib/rubino/cli/trust_gate.rb +71 -0
- data/lib/rubino/commands/built_ins.rb +46 -0
- data/lib/rubino/commands/command.rb +116 -0
- data/lib/rubino/commands/executor.rb +550 -0
- data/lib/rubino/commands/handlers/agents.rb +510 -0
- data/lib/rubino/commands/handlers/config.rb +88 -0
- data/lib/rubino/commands/handlers/help.rb +148 -0
- data/lib/rubino/commands/handlers/jobs.rb +71 -0
- data/lib/rubino/commands/handlers/mcp.rb +229 -0
- data/lib/rubino/commands/handlers/memory.rb +200 -0
- data/lib/rubino/commands/handlers/sessions.rb +207 -0
- data/lib/rubino/commands/handlers/skills.rb +195 -0
- data/lib/rubino/commands/handlers/status.rb +211 -0
- data/lib/rubino/commands/loader.rb +90 -0
- data/lib/rubino/config/configuration.rb +455 -0
- data/lib/rubino/config/defaults.rb +569 -0
- data/lib/rubino/config/loader.rb +115 -0
- data/lib/rubino/config/reasoning_prefs.rb +67 -0
- data/lib/rubino/config/writer.rb +72 -0
- data/lib/rubino/context/compressor.rb +149 -0
- data/lib/rubino/context/environment_inspector.rb +176 -0
- data/lib/rubino/context/file_discovery.rb +45 -0
- data/lib/rubino/context/message_boundary.rb +39 -0
- data/lib/rubino/context/prompt_assembler.rb +382 -0
- data/lib/rubino/context/summary_builder.rb +159 -0
- data/lib/rubino/context/token_budget.rb +68 -0
- data/lib/rubino/context/tool_pair_sanitizer.rb +70 -0
- data/lib/rubino/database/connection.rb +77 -0
- data/lib/rubino/database/migrations/001_create_initial_schema.rb +156 -0
- data/lib/rubino/database/migrations/002_create_runs.rb +45 -0
- data/lib/rubino/database/migrations/003_create_skill_states.rb +15 -0
- data/lib/rubino/database/migrations/004_create_cron_jobs.rb +36 -0
- data/lib/rubino/database/migrations/005_create_oauth_connections.rb +27 -0
- data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +34 -0
- data/lib/rubino/database/migrations/007_create_messages_fts.rb +59 -0
- data/lib/rubino/database/migrations/008_create_memory_facts.rb +75 -0
- data/lib/rubino/database/migrations/009_create_memory_graph.rb +55 -0
- data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +20 -0
- data/lib/rubino/database/migrator.rb +48 -0
- data/lib/rubino/documents/converters/csv.rb +79 -0
- data/lib/rubino/documents/converters/docx.rb +129 -0
- data/lib/rubino/documents/converters/html.rb +28 -0
- data/lib/rubino/documents/converters/json.rb +35 -0
- data/lib/rubino/documents/converters/pdf.rb +59 -0
- data/lib/rubino/documents/converters/plain.rb +68 -0
- data/lib/rubino/documents/converters/pptx.rb +64 -0
- data/lib/rubino/documents/converters/xlsx.rb +62 -0
- data/lib/rubino/documents/converters/xml.rb +45 -0
- data/lib/rubino/documents/html.rb +71 -0
- data/lib/rubino/documents/registry.rb +68 -0
- data/lib/rubino/documents/table.rb +63 -0
- data/lib/rubino/documents.rb +50 -0
- data/lib/rubino/errors.rb +119 -0
- data/lib/rubino/files/workspace.rb +93 -0
- data/lib/rubino/interaction/cancel_token.rb +43 -0
- data/lib/rubino/interaction/clipboard_image.rb +84 -0
- data/lib/rubino/interaction/event_bus.rb +48 -0
- data/lib/rubino/interaction/events.rb +101 -0
- data/lib/rubino/interaction/image_input.rb +127 -0
- data/lib/rubino/interaction/input_queue.rb +117 -0
- data/lib/rubino/interaction/lifecycle.rb +299 -0
- data/lib/rubino/interaction/probe.rb +65 -0
- data/lib/rubino/interaction/state.rb +56 -0
- data/lib/rubino/jobs/cron_job_repository.rb +75 -0
- data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +32 -0
- data/lib/rubino/jobs/handlers/compact_session_job.rb +21 -0
- data/lib/rubino/jobs/handlers/distill_skill_job.rb +186 -0
- data/lib/rubino/jobs/handlers/extract_memory_job.rb +37 -0
- data/lib/rubino/jobs/handlers/summarize_session_job.rb +21 -0
- data/lib/rubino/jobs/queue.rb +184 -0
- data/lib/rubino/jobs/registry.rb +45 -0
- data/lib/rubino/jobs/runner.rb +79 -0
- data/lib/rubino/jobs/scheduler.rb +138 -0
- data/lib/rubino/jobs/webhook_delivery.rb +225 -0
- data/lib/rubino/jobs/worker.rb +59 -0
- data/lib/rubino/llm/adapter_factory.rb +47 -0
- data/lib/rubino/llm/adapter_response.rb +65 -0
- data/lib/rubino/llm/auxiliary_client.rb +61 -0
- data/lib/rubino/llm/bedrock_bearer_client.rb +235 -0
- data/lib/rubino/llm/content_builder.rb +55 -0
- data/lib/rubino/llm/credential_check.rb +93 -0
- data/lib/rubino/llm/error_classifier.rb +364 -0
- data/lib/rubino/llm/fake_provider.rb +292 -0
- data/lib/rubino/llm/inline_think_filter.rb +58 -0
- data/lib/rubino/llm/model_catalog.rb +29 -0
- data/lib/rubino/llm/provider_resolver.rb +48 -0
- data/lib/rubino/llm/reasoning_manager.rb +100 -0
- data/lib/rubino/llm/request.rb +56 -0
- data/lib/rubino/llm/ruby_llm_adapter.rb +794 -0
- data/lib/rubino/llm/scenario_loader.rb +68 -0
- data/lib/rubino/llm/scenario_selector.rb +80 -0
- data/lib/rubino/llm/scenarios/agent-creates-cron-failure.yml +29 -0
- data/lib/rubino/llm/scenarios/agent-creates-cron.yml +36 -0
- data/lib/rubino/llm/scenarios/analysis.yml +501 -0
- data/lib/rubino/llm/scenarios/complex-analysis.yml +598 -0
- data/lib/rubino/llm/scenarios/failure.yml +65 -0
- data/lib/rubino/llm/scenarios/happy-path.yml +24 -0
- data/lib/rubino/llm/scenarios/provider-quota-completed.yml +14 -0
- data/lib/rubino/llm/scenarios/wide-table.yml +121 -0
- data/lib/rubino/llm/scenarios/with-approvals.yml +50 -0
- data/lib/rubino/llm/scenarios/with-artifacts.yml +98 -0
- data/lib/rubino/llm/scenarios/with-clarify.yml +32 -0
- data/lib/rubino/llm/scenarios/with-reasoning.yml +175 -0
- data/lib/rubino/llm/scenarios/with-uploads.yml +104 -0
- data/lib/rubino/llm/thinking_support.rb +84 -0
- data/lib/rubino/llm/tool_bridge.rb +89 -0
- data/lib/rubino/logger.rb +99 -0
- data/lib/rubino/mcp/manager.rb +180 -0
- data/lib/rubino/mcp/mcp_tool_wrapper.rb +69 -0
- data/lib/rubino/mcp.rb +57 -0
- data/lib/rubino/memory/backend.rb +104 -0
- data/lib/rubino/memory/backends/default.rb +101 -0
- data/lib/rubino/memory/backends/sqlite.rb +653 -0
- data/lib/rubino/memory/backends.rb +53 -0
- data/lib/rubino/memory/deduplicator.rb +74 -0
- data/lib/rubino/memory/extractor.rb +85 -0
- data/lib/rubino/memory/flusher.rb +31 -0
- data/lib/rubino/memory/retriever.rb +50 -0
- data/lib/rubino/memory/sqlite_extraction_prompt.rb +70 -0
- data/lib/rubino/memory/sqlite_graph.rb +154 -0
- data/lib/rubino/memory/store.rb +228 -0
- data/lib/rubino/memory/threat_scanner.rb +68 -0
- data/lib/rubino/metrics.rb +175 -0
- data/lib/rubino/modes.rb +93 -0
- data/lib/rubino/oauth/connection_repository.rb +95 -0
- data/lib/rubino/oauth/provider/github.rb +75 -0
- data/lib/rubino/oauth/provider/google.rb +59 -0
- data/lib/rubino/oauth/provider.rb +149 -0
- data/lib/rubino/oauth/registry.rb +86 -0
- data/lib/rubino/oauth/token_encryptor.rb +87 -0
- data/lib/rubino/plugins/registry.rb +75 -0
- data/lib/rubino/plugins.rb +86 -0
- data/lib/rubino/run/approval_gate.rb +243 -0
- data/lib/rubino/run/attachment_downloader.rb +166 -0
- data/lib/rubino/run/event_store.rb +74 -0
- data/lib/rubino/run/executor.rb +383 -0
- data/lib/rubino/run/gate_registry.rb +39 -0
- data/lib/rubino/run/recorder.rb +69 -0
- data/lib/rubino/run/repository.rb +118 -0
- data/lib/rubino/run/session_approval_cache.rb +118 -0
- data/lib/rubino/security/allowlist_persister.rb +55 -0
- data/lib/rubino/security/approval_policy.rb +227 -0
- data/lib/rubino/security/command_allowlist.rb +24 -0
- data/lib/rubino/security/dangerous_patterns.rb +118 -0
- data/lib/rubino/security/deny_persister.rb +73 -0
- data/lib/rubino/security/doom_loop_detector.rb +43 -0
- data/lib/rubino/security/hardline_guard.rb +105 -0
- data/lib/rubino/security/pattern_matcher.rb +62 -0
- data/lib/rubino/security/prefix_deriver.rb +124 -0
- data/lib/rubino/security/readonly_commands.rb +211 -0
- data/lib/rubino/session/exporter.rb +101 -0
- data/lib/rubino/session/message.rb +77 -0
- data/lib/rubino/session/repository.rb +295 -0
- data/lib/rubino/session/store.rb +198 -0
- data/lib/rubino/session/summary_store.rb +65 -0
- data/lib/rubino/skills/prompt_index.rb +85 -0
- data/lib/rubino/skills/registry.rb +208 -0
- data/lib/rubino/skills/skill.rb +176 -0
- data/lib/rubino/skills/skill_tool.rb +215 -0
- data/lib/rubino/skills/state_repository.rb +37 -0
- data/lib/rubino/skills/toggle.rb +26 -0
- data/lib/rubino/tools/answer_child_tool.rb +83 -0
- data/lib/rubino/tools/ask_parent_tool.rb +232 -0
- data/lib/rubino/tools/attach_file_tool.rb +120 -0
- data/lib/rubino/tools/background_tasks.rb +520 -0
- data/lib/rubino/tools/base.rb +222 -0
- data/lib/rubino/tools/custom_tool_loader.rb +119 -0
- data/lib/rubino/tools/edit_tool.rb +122 -0
- data/lib/rubino/tools/git_tool.rb +71 -0
- data/lib/rubino/tools/github_tool.rb +233 -0
- data/lib/rubino/tools/glob_tool.rb +69 -0
- data/lib/rubino/tools/grep_tool.rb +206 -0
- data/lib/rubino/tools/memory_tool.rb +184 -0
- data/lib/rubino/tools/multi_edit_tool.rb +110 -0
- data/lib/rubino/tools/patch_tool.rb +260 -0
- data/lib/rubino/tools/probe_tool.rb +175 -0
- data/lib/rubino/tools/question_tool.rb +128 -0
- data/lib/rubino/tools/read_attachment_tool.rb +180 -0
- data/lib/rubino/tools/read_tool.rb +212 -0
- data/lib/rubino/tools/read_tracker.rb +98 -0
- data/lib/rubino/tools/registry.rb +166 -0
- data/lib/rubino/tools/result.rb +113 -0
- data/lib/rubino/tools/ruby_tool.rb +0 -0
- data/lib/rubino/tools/session_search_tool.rb +103 -0
- data/lib/rubino/tools/shell_input_tool.rb +96 -0
- data/lib/rubino/tools/shell_kill_tool.rb +76 -0
- data/lib/rubino/tools/shell_output_tool.rb +72 -0
- data/lib/rubino/tools/shell_registry.rb +158 -0
- data/lib/rubino/tools/shell_tail_tool.rb +118 -0
- data/lib/rubino/tools/shell_tool.rb +330 -0
- data/lib/rubino/tools/steer_tool.rb +118 -0
- data/lib/rubino/tools/subagent_probe.rb +89 -0
- data/lib/rubino/tools/summarize_file_tool.rb +182 -0
- data/lib/rubino/tools/task_result_tool.rb +90 -0
- data/lib/rubino/tools/task_stop_tool.rb +80 -0
- data/lib/rubino/tools/task_tool.rb +622 -0
- data/lib/rubino/tools/test_tool.rb +454 -0
- data/lib/rubino/tools/todo_tool.rb +93 -0
- data/lib/rubino/tools/tool_call_repository.rb +33 -0
- data/lib/rubino/tools/vision_tool.rb +85 -0
- data/lib/rubino/tools/webfetch_tool.rb +153 -0
- data/lib/rubino/tools/websearch_tool.rb +179 -0
- data/lib/rubino/tools/write_tool.rb +61 -0
- data/lib/rubino/trust.rb +88 -0
- data/lib/rubino/ui/api.rb +296 -0
- data/lib/rubino/ui/base.rb +252 -0
- data/lib/rubino/ui/bottom_composer.rb +1599 -0
- data/lib/rubino/ui/cli.rb +1987 -0
- data/lib/rubino/ui/completion_menu.rb +321 -0
- data/lib/rubino/ui/completion_source.rb +284 -0
- data/lib/rubino/ui/escape_reader.rb +169 -0
- data/lib/rubino/ui/indented_io.rb +88 -0
- data/lib/rubino/ui/input_history.rb +108 -0
- data/lib/rubino/ui/live_region.rb +183 -0
- data/lib/rubino/ui/markdown_renderer.rb +506 -0
- data/lib/rubino/ui/notifier.rb +163 -0
- data/lib/rubino/ui/null.rb +195 -0
- data/lib/rubino/ui/paste_store.rb +176 -0
- data/lib/rubino/ui/printer_base.rb +79 -0
- data/lib/rubino/ui/probe_wait_indicator.rb +75 -0
- data/lib/rubino/ui/queued_indicators.rb +66 -0
- data/lib/rubino/ui/status_bar.rb +100 -0
- data/lib/rubino/ui/stdout_proxy.rb +161 -0
- data/lib/rubino/ui/streaming_markdown.rb +186 -0
- data/lib/rubino/ui/subagent_cards.rb +134 -0
- data/lib/rubino/ui/subagent_view.rb +255 -0
- data/lib/rubino/ui.rb +21 -0
- data/lib/rubino/update_check.rb +187 -0
- data/lib/rubino/util/duration.rb +23 -0
- data/lib/rubino/util/hyperlink.rb +105 -0
- data/lib/rubino/util/output.rb +145 -0
- data/lib/rubino/util/secrets_mask.rb +83 -0
- data/lib/rubino/version.rb +5 -0
- data/lib/rubino/workspace.rb +85 -0
- data/lib/rubino-agent.rb +5 -0
- data/lib/rubino.rb +318 -0
- data/mise.toml +2 -0
- data/rubino-agent.gemspec +103 -0
- data/skills/ruby-expert/SKILL.md +67 -0
- data/skills/ruby-expert/references/concurrency.md +357 -0
- data/skills/ruby-expert/references/datetime-and-encoding.md +363 -0
- data/skills/ruby-expert/references/errors-and-types.md +460 -0
- data/skills/ruby-expert/references/gem-authoring.md +459 -0
- data/skills/ruby-expert/references/language-idioms.md +465 -0
- data/skills/ruby-expert/references/metaprogramming.md +339 -0
- data/skills/ruby-expert/references/oo-design.md +553 -0
- data/skills/ruby-expert/references/performance.md +383 -0
- data/skills/ruby-expert/references/rails.md +424 -0
- data/skills/ruby-expert/references/security.md +404 -0
- data/skills/ruby-expert/references/testing.md +473 -0
- data/skills/ruby-expert/references/tooling.md +466 -0
- metadata +856 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module Commands
|
|
5
|
+
# Discovers and manages custom slash commands from configured paths.
|
|
6
|
+
class Loader
|
|
7
|
+
COMMAND_GLOB = "*.md"
|
|
8
|
+
|
|
9
|
+
def initialize(config: nil)
|
|
10
|
+
@config = config || Rubino.configuration
|
|
11
|
+
@commands = {}
|
|
12
|
+
@discovered = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Discovers all available commands
|
|
16
|
+
def discover!
|
|
17
|
+
@commands.clear
|
|
18
|
+
command_paths.each do |dir|
|
|
19
|
+
expanded = self.class.resolve_path(dir)
|
|
20
|
+
next unless File.directory?(expanded)
|
|
21
|
+
|
|
22
|
+
Dir.glob(File.join(expanded, COMMAND_GLOB)).each do |path|
|
|
23
|
+
cmd = Command.new(path: path)
|
|
24
|
+
@commands[cmd.name] = cmd
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
@discovered = true
|
|
28
|
+
@commands
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns all discovered commands
|
|
32
|
+
def all
|
|
33
|
+
discover! unless @discovered
|
|
34
|
+
@commands.values
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Finds a command by name (without the leading /)
|
|
38
|
+
def find(name)
|
|
39
|
+
discover! unless @discovered
|
|
40
|
+
@commands[name.to_s.sub(%r{\A/}, "")]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns true if input starts with a slash command
|
|
44
|
+
def slash_command?(input)
|
|
45
|
+
input.strip.start_with?("/")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Parses a slash command input into [command_name, arguments]
|
|
49
|
+
def parse(input)
|
|
50
|
+
stripped = input.strip
|
|
51
|
+
return nil unless stripped.start_with?("/")
|
|
52
|
+
|
|
53
|
+
parts = stripped[1..].split(/\s+/, 2)
|
|
54
|
+
command_name = parts[0]
|
|
55
|
+
arguments = parts[1] || ""
|
|
56
|
+
[command_name, arguments]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns command names for autocomplete
|
|
60
|
+
def names
|
|
61
|
+
all.map { |c| "/#{c.name}" }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def command_paths
|
|
67
|
+
@config.dig("commands", "paths") || Config::Defaults.to_hash.dig("commands", "paths")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Default search paths, with the home sentinel resolved to a real dir.
|
|
71
|
+
# Used by the loader and the "/commands" empty-state copy so both report
|
|
72
|
+
# the directories actually searched (RUBINO_HOME-aware).
|
|
73
|
+
def self.default_command_paths
|
|
74
|
+
Array(Config::Defaults.to_hash.dig("commands", "paths")).map { |p| resolve_path(p) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Resolves a configured commands path to an absolute directory, expanding
|
|
78
|
+
# the <RUBINO_HOME>/commands sentinel against the resolved home
|
|
79
|
+
# (RUBINO_HOME -> else ~/.rubino) instead of a literal ~/.rubino (#38).
|
|
80
|
+
def self.resolve_path(dir)
|
|
81
|
+
if dir.to_s.start_with?(Config::Defaults::HOME_COMMANDS_PATH)
|
|
82
|
+
suffix = dir.to_s.sub(Config::Defaults::HOME_COMMANDS_PATH, "")
|
|
83
|
+
File.join(Config::Loader.default_home_path, "commands#{suffix}")
|
|
84
|
+
else
|
|
85
|
+
File.expand_path(dir)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubino
|
|
4
|
+
module Config
|
|
5
|
+
# Central configuration object providing typed accessors for all config sections.
|
|
6
|
+
# Wraps the raw hash loaded by Config::Loader with convenient method access.
|
|
7
|
+
class Configuration
|
|
8
|
+
attr_reader :raw
|
|
9
|
+
|
|
10
|
+
def initialize(raw: nil, home_path: nil)
|
|
11
|
+
@home_path = home_path
|
|
12
|
+
@raw = raw || load_from_file
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# -- Model section --
|
|
16
|
+
def model_default
|
|
17
|
+
dig("model", "default")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def model_provider
|
|
21
|
+
dig("model", "provider")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def model_context_length
|
|
25
|
+
dig("model", "context_length")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def model_temperature
|
|
29
|
+
dig("model", "temperature")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# -- Database section --
|
|
33
|
+
# Resolves the sqlite path. The DEFAULT (sentinel) follows the resolved
|
|
34
|
+
# home so RUBINO_HOME relocates the DB alongside config/.env/skills,
|
|
35
|
+
# avoiding the split brain where config went to the isolated home but the
|
|
36
|
+
# DB to the real ~/.rubino (issue #96). An EXPLICIT database.path in
|
|
37
|
+
# config.yml wins and is expanded verbatim.
|
|
38
|
+
def database_path
|
|
39
|
+
path = dig("database", "path")
|
|
40
|
+
if path == Defaults::DEFAULT_DATABASE_PATH
|
|
41
|
+
File.join(resolved_home, "rubino.sqlite3")
|
|
42
|
+
else
|
|
43
|
+
File.expand_path(path)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# -- Paths section --
|
|
48
|
+
def paths_home
|
|
49
|
+
dig("paths", "home")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# -- UI section --
|
|
53
|
+
def ui_adapter
|
|
54
|
+
dig("ui", "adapter")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ui_verbose?
|
|
58
|
+
dig("ui", "verbose") == true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# -- Display section --
|
|
62
|
+
def display_streaming?
|
|
63
|
+
dig("display", "streaming") == true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The status bar under the chat input (display.statusbar, default true).
|
|
67
|
+
# Only an explicit false disables it.
|
|
68
|
+
def display_statusbar?
|
|
69
|
+
dig("display", "statusbar") != false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Transcript preview budget for tool output
|
|
73
|
+
# (display.tool_output_preview_lines): head lines shown before the
|
|
74
|
+
# "… +N lines (full output → context)" marker. 0 = no collapse (full
|
|
75
|
+
# dump). Display-only — the model-facing output is untouched.
|
|
76
|
+
def display_tool_output_preview_lines
|
|
77
|
+
value = dig("display", "tool_output_preview_lines")
|
|
78
|
+
value.nil? ? 3 : value.to_i
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Cap on the chat input's visual rows (display.input_max_rows). Falls
|
|
82
|
+
# back to the composer default for nil/zero/garbage so a bad value can
|
|
83
|
+
# never collapse or unbound the input block.
|
|
84
|
+
def display_input_max_rows
|
|
85
|
+
value = dig("display", "input_max_rows").to_i
|
|
86
|
+
value.positive? ? value : UI::BottomComposer::MAX_INPUT_ROWS
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# -- Paste section (UI::PasteStore: the file-backed paste pipeline) --
|
|
90
|
+
# A paste with MORE than this many lines collapses to a
|
|
91
|
+
# "[Pasted text #N +M lines]" placeholder in the composer (expanded to
|
|
92
|
+
# the full body at send). Falls back for nil/zero/garbage.
|
|
93
|
+
def paste_collapse_lines
|
|
94
|
+
value = dig("paste", "collapse_lines").to_i
|
|
95
|
+
value.positive? ? value : UI::PasteStore::DEFAULT_COLLAPSE_LINES
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# A paste estimated above this many tokens (chars/4, the same rule
|
|
99
|
+
# compaction uses) overflows to <home>/sessions/<id>/paste_N.txt and the
|
|
100
|
+
# message carries a read-tool pointer instead of the content.
|
|
101
|
+
def paste_file_threshold_tokens
|
|
102
|
+
value = dig("paste", "file_threshold_tokens").to_i
|
|
103
|
+
value.positive? ? value : UI::PasteStore::DEFAULT_THRESHOLD_TOKENS
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# -- Notifications section (UI::Notifier: attention bell + hook) --
|
|
107
|
+
# enabled/bell are on unless explicitly false; command is nil unless a
|
|
108
|
+
# non-empty string is set; min_turn_seconds falls back to the default.
|
|
109
|
+
def notifications_enabled?
|
|
110
|
+
dig("notifications", "enabled") != false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def notifications_bell?
|
|
114
|
+
dig("notifications", "bell") != false
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def notifications_command
|
|
118
|
+
value = dig("notifications", "command").to_s
|
|
119
|
+
value.empty? ? nil : value
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def notifications_min_turn_seconds
|
|
123
|
+
value = dig("notifications", "min_turn_seconds")
|
|
124
|
+
(value.nil? ? Defaults.dig("notifications", "min_turn_seconds") : value).to_f
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# -- Streaming section --
|
|
128
|
+
def streaming_enabled?
|
|
129
|
+
dig("streaming", "enabled") == true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# -- Agent section --
|
|
133
|
+
def agent_max_turns
|
|
134
|
+
dig("agent", "max_turns")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Iteration/time caps fall back to the built-in defaults when the config
|
|
138
|
+
# value is nil/missing (e.g. `config set agent.max_tool_iterations nil`,
|
|
139
|
+
# whose writer coerces "nil" -> nil). A bare nil here would crash every
|
|
140
|
+
# turn in IterationBudget's numeric comparisons (#139).
|
|
141
|
+
def agent_max_tool_iterations
|
|
142
|
+
dig("agent", "max_tool_iterations") || Defaults.dig("agent", "max_tool_iterations")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def agent_max_turn_seconds
|
|
146
|
+
dig("agent", "max_turn_seconds") || Defaults.dig("agent", "max_turn_seconds")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def agent_api_max_retries
|
|
150
|
+
dig("agent", "api_max_retries")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def agent_disabled_toolsets
|
|
154
|
+
dig("agent", "disabled_toolsets") || []
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# -- Tasks / nested-subagent caps --
|
|
158
|
+
# Maximum nesting depth for the `task` delegation tree. depth 0 is a
|
|
159
|
+
# human/top-level-spawned child; the cap bounds how deep a chain of
|
|
160
|
+
# subagents-spawning-subagents may go. Default 2 ⇒ human→child→grandchild.
|
|
161
|
+
# Falls back to the built-in default when missing/nil so the numeric caps
|
|
162
|
+
# in BackgroundTask#reserve never crash on a bare nil.
|
|
163
|
+
def tasks_max_depth
|
|
164
|
+
dig("tasks", "max_depth") || Defaults.dig("tasks", "max_depth")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Maximum number of LIVE direct children a single node (the human/top-level
|
|
168
|
+
# or one subagent) may have at once. Default 3.
|
|
169
|
+
def tasks_max_children_per_node
|
|
170
|
+
dig("tasks", "max_children_per_node") || Defaults.dig("tasks", "max_children_per_node")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Hard global ceiling on the total number of LIVE subagents across the whole
|
|
174
|
+
# tree, so depth × fan-out cannot blow past the process's thread/cost budget.
|
|
175
|
+
# Default 8.
|
|
176
|
+
def tasks_max_concurrent_total
|
|
177
|
+
dig("tasks", "max_concurrent_total") || Defaults.dig("tasks", "max_concurrent_total")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Per-child budget for BILLED live probes (`probe(live:true)`). Over budget,
|
|
181
|
+
# the model is steered to the FREE live:false snapshot. Free snapshots are
|
|
182
|
+
# unlimited. Default 5.
|
|
183
|
+
def tasks_max_live_probes_per_child
|
|
184
|
+
dig("tasks", "max_live_probes_per_child") || Defaults.dig("tasks", "max_live_probes_per_child")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Bound (seconds) a BLOCKING ask_parent waits for an answer before the child
|
|
188
|
+
# self-heals and proceeds with its best judgement (S5a). Reuses the
|
|
189
|
+
# approval-gate timeout convention — a sane upper bound, never "forever" —
|
|
190
|
+
# so an abandoned ask never parks the child's thread indefinitely. Default 900.
|
|
191
|
+
def tasks_ask_parent_timeout
|
|
192
|
+
dig("tasks", "ask_parent_timeout") || Defaults.dig("tasks", "ask_parent_timeout")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# -- Prompts section --
|
|
196
|
+
# The customer-facing preamble prepended to every assembled system
|
|
197
|
+
# prompt. nil/empty disables the layer.
|
|
198
|
+
def prompts_preamble
|
|
199
|
+
value = dig("prompts", "preamble")
|
|
200
|
+
return nil if value.nil?
|
|
201
|
+
|
|
202
|
+
text = value.to_s.strip
|
|
203
|
+
text.empty? ? nil : text
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def prompts_environment_enabled?
|
|
207
|
+
# Default to on when the key is absent — env injection is the cheap
|
|
208
|
+
# win we don't want a forgetful config.yml to disable accidentally.
|
|
209
|
+
value = dig("prompts", "environment", "enabled")
|
|
210
|
+
value.nil? || value == true
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def prompts_environment_extra_utilities
|
|
214
|
+
Array(dig("prompts", "environment", "extra_utilities")).map(&:to_s)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Returns the override string for a given role name, or nil if the
|
|
218
|
+
# built-in default prompt should be used.
|
|
219
|
+
def prompts_override_for(role)
|
|
220
|
+
value = dig("prompts", "overrides", role.to_s)
|
|
221
|
+
return nil if value.nil?
|
|
222
|
+
|
|
223
|
+
text = value.to_s.strip
|
|
224
|
+
text.empty? ? nil : text
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# -- Run lifecycle section --
|
|
228
|
+
# Returns Float seconds (or nil to disable). EventsOperation uses this
|
|
229
|
+
# to bound how long a "running" row can go without producing a new
|
|
230
|
+
# event before the watchdog promotes it to failed.
|
|
231
|
+
def run_idle_event_timeout
|
|
232
|
+
raw = dig("run", "idle_event_timeout")
|
|
233
|
+
return nil if raw.nil?
|
|
234
|
+
|
|
235
|
+
raw.to_f
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# -- Compression section --
|
|
239
|
+
def compression_enabled?
|
|
240
|
+
dig("compression", "enabled") == true
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def compression_threshold
|
|
244
|
+
dig("compression", "threshold")
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def compression_gateway_threshold
|
|
248
|
+
dig("compression", "gateway_threshold")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def compression_target_ratio
|
|
252
|
+
dig("compression", "target_ratio")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def compression_protect_first_n
|
|
256
|
+
dig("compression", "protect_first_n")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def compression_protect_last_n
|
|
260
|
+
dig("compression", "protect_last_n")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def compression_max_summary_tokens
|
|
264
|
+
dig("compression", "max_summary_tokens")
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def compression_preserve_tool_pairs?
|
|
268
|
+
dig("compression", "preserve_tool_pairs") == true
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# -- Memory section --
|
|
272
|
+
def memory_enabled?
|
|
273
|
+
dig("memory", "enabled") == true
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def memory_auto_extract?
|
|
277
|
+
dig("memory", "auto_extract") == true
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def memory_char_limit
|
|
281
|
+
dig("memory", "memory_char_limit")
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Post-turn skill distillation. Defaults to true (skills feature on +
|
|
285
|
+
# distill key absent ⇒ distill on), mirroring memory_auto_extract? as the
|
|
286
|
+
# gate for an aux-spending background job. Turning skills off disables it
|
|
287
|
+
# too, since there is no point distilling skills that won't be loaded.
|
|
288
|
+
def skills_auto_distill?
|
|
289
|
+
return false unless dig("skills", "enabled") != false
|
|
290
|
+
|
|
291
|
+
value = dig("skills", "auto_distill")
|
|
292
|
+
value.nil? || value == true
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def memory_user_char_limit
|
|
296
|
+
dig("memory", "user_char_limit")
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Ingest/store budget for the live memory set, decoupled from the
|
|
300
|
+
# injection budget (`memory_char_limit`). `nil` => unbounded ingest.
|
|
301
|
+
def memory_ingest_char_limit
|
|
302
|
+
dig("memory", "ingest_char_limit")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# -- Jobs section --
|
|
306
|
+
def jobs_mode
|
|
307
|
+
dig("jobs", "mode")
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def jobs_poll_interval
|
|
311
|
+
dig("jobs", "poll_interval")
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def jobs_max_attempts
|
|
315
|
+
dig("jobs", "max_attempts")
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# -- Tools section --
|
|
319
|
+
def tool_enabled?(name)
|
|
320
|
+
dig("tools", name.to_s) == true
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def tool_output_max_bytes
|
|
324
|
+
dig("tool_output", "max_bytes")
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def tool_output_max_lines
|
|
328
|
+
dig("tool_output", "max_lines")
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# -- Security section --
|
|
332
|
+
def approvals_mode
|
|
333
|
+
dig("approvals", "mode")
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Seconds a run blocks on a human approval/clarification before the gate
|
|
337
|
+
# gives up and AUTO-DENIES (freeing the worker thread). nil = wait
|
|
338
|
+
# indefinitely (interruptible only by an explicit stop). Used by
|
|
339
|
+
# ApprovalGate as its default await deadline so an abandoned approval
|
|
340
|
+
# never parks a server worker for the whole window (W1).
|
|
341
|
+
def approvals_wait_timeout
|
|
342
|
+
raw = dig("approvals", "wait_timeout_seconds")
|
|
343
|
+
return nil if raw.nil?
|
|
344
|
+
|
|
345
|
+
raw.to_f
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Auto-allow provably read-only shell commands (ls, cat, grep, git log,
|
|
349
|
+
# ...) without an approval prompt. Default ON (key absent = on); the
|
|
350
|
+
# hardline floor and permissions:deny still precede it.
|
|
351
|
+
def auto_allow_readonly?
|
|
352
|
+
dig("approvals", "auto_allow_readonly") != false
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Extra command names / leading-token prefixes merged into the built-in
|
|
356
|
+
# read-only set (Security::ReadonlyCommands::SAFE_COMMANDS).
|
|
357
|
+
def approvals_readonly_commands
|
|
358
|
+
dig("approvals", "readonly_commands") || []
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# When true, a `shell` tool call must always be confirmed in manual mode
|
|
362
|
+
# even if the tool's own risk level wouldn't otherwise require it. Default
|
|
363
|
+
# true (key absent = on) so shell-by-default stays gated behind a human.
|
|
364
|
+
def require_confirmation_for_shell?
|
|
365
|
+
dig("security", "require_confirmation_for_shell") != false
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Effective shell prompt policy: :confirm_all (every not-otherwise-allowed
|
|
369
|
+
# shell command prompts — today's default) or :dangerous_only (safe shell
|
|
370
|
+
# commands run unprompted; only DangerousPatterns matches prompt).
|
|
371
|
+
#
|
|
372
|
+
# Resolution / coercion (documented in defaults.rb):
|
|
373
|
+
# - if security.confirm_policy is set explicitly, it WINS (over the
|
|
374
|
+
# legacy alias);
|
|
375
|
+
# - otherwise it is DERIVED from require_confirmation_for_shell
|
|
376
|
+
# (true -> :confirm_all, false -> :dangerous_only),
|
|
377
|
+
# so any deployment that only ever set the old alias keeps its behavior.
|
|
378
|
+
# An unrecognized value falls back to the derived alias result.
|
|
379
|
+
def confirm_policy
|
|
380
|
+
raw = dig("security", "confirm_policy")
|
|
381
|
+
return raw.to_sym if %w[confirm_all dangerous_only].include?(raw.to_s)
|
|
382
|
+
|
|
383
|
+
require_confirmation_for_shell? ? :confirm_all : :dangerous_only
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def security_command_allowlist
|
|
387
|
+
dig("security", "command_allowlist") || []
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# -- Providers section --
|
|
391
|
+
def provider_config(name)
|
|
392
|
+
dig("providers", name.to_s) || {}
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# -- Auxiliary section --
|
|
396
|
+
def auxiliary_compression_config
|
|
397
|
+
dig("auxiliary", "compression") || {}
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def auxiliary_vision_config
|
|
401
|
+
dig("auxiliary", "vision") || {}
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Generic accessor for auxiliary task config blocks. Returns {} when
|
|
405
|
+
# the task isn't defined, so callers can chain .dig safely.
|
|
406
|
+
def auxiliary_config(task)
|
|
407
|
+
dig("auxiliary", task.to_s) || {}
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Returns true when the primary model can ingest images directly. Honours
|
|
411
|
+
# an explicit `model.supports_vision` override; otherwise falls back to
|
|
412
|
+
# ContentBuilder's name-pattern heuristic. Used by VisionTool to decide
|
|
413
|
+
# whether to expose itself (no point delegating if the primary can see).
|
|
414
|
+
def model_supports_vision?
|
|
415
|
+
raw = dig("model", "supports_vision")
|
|
416
|
+
return raw == true unless raw.nil?
|
|
417
|
+
|
|
418
|
+
LLM::ContentBuilder.supports_vision?(model_default.to_s)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# -- Generic access --
|
|
422
|
+
def dig(*keys)
|
|
423
|
+
@raw.dig(*keys)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def set(*keys, value)
|
|
427
|
+
hash = @raw
|
|
428
|
+
keys[0..-2].each do |key|
|
|
429
|
+
hash[key] ||= {}
|
|
430
|
+
hash = hash[key]
|
|
431
|
+
end
|
|
432
|
+
hash[keys.last] = value
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def reload!
|
|
436
|
+
@raw = load_from_file
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
private
|
|
440
|
+
|
|
441
|
+
# The home this config is bound to: the explicit home_path passed at
|
|
442
|
+
# construction, else the same resolver the Loader uses (RUBINO_HOME →
|
|
443
|
+
# ~/.rubino). Read here (not at construction) so RUBINO_HOME just
|
|
444
|
+
# needs to be set before database_path is first read.
|
|
445
|
+
def resolved_home
|
|
446
|
+
@home_path || Loader.default_home_path
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def load_from_file
|
|
450
|
+
loader = Loader.new(home_path: @home_path)
|
|
451
|
+
loader.load
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|