Module: Rubino
- Defined in:
- lib/rubino.rb,
lib/rubino.rb,
lib/rubino/ui.rb,
lib/rubino/mcp.rb,
lib/rubino/modes.rb,
lib/rubino/trust.rb,
lib/rubino/errors.rb,
lib/rubino/logger.rb,
lib/rubino/memory.rb,
lib/rubino/ui/api.rb,
lib/rubino/ui/cli.rb,
lib/rubino/api/tls.rb,
lib/rubino/metrics.rb,
lib/rubino/ui/base.rb,
lib/rubino/ui/null.rb,
lib/rubino/version.rb,
lib/rubino/documents.rb,
lib/rubino/workspace.rb,
lib/rubino/agent/loop.rb,
lib/rubino/api/router.rb,
lib/rubino/api/server.rb,
lib/rubino/jobs/queue.rb,
lib/rubino/tools/base.rb,
lib/rubino/api/request.rb,
lib/rubino/api/schemas.rb,
lib/rubino/jobs/runner.rb,
lib/rubino/jobs/worker.rb,
lib/rubino/llm/request.rb,
lib/rubino/mcp/manager.rb,
lib/rubino/output/cost.rb,
lib/rubino/ui/notifier.rb,
lib/rubino/util/output.rb,
lib/rubino/active_agent.rb,
lib/rubino/active_skill.rb,
lib/rubino/agent/runner.rb,
lib/rubino/cli/commands.rb,
lib/rubino/memory/store.rb,
lib/rubino/run/executor.rb,
lib/rubino/run/recorder.rb,
lib/rubino/session/lock.rb,
lib/rubino/skills/skill.rb,
lib/rubino/tools/result.rb,
lib/rubino/ui/menu_view.rb,
lib/rubino/update_check.rb,
lib/rubino/api/responses.rb,
lib/rubino/config/loader.rb,
lib/rubino/config/writer.rb,
lib/rubino/jobs/registry.rb,
lib/rubino/session/store.rb,
lib/rubino/skills/toggle.rb,
lib/rubino/ui/agent_menu.rb,
lib/rubino/ui/status_bar.rb,
lib/rubino/ui/tool_label.rb,
lib/rubino/util/duration.rb,
lib/rubino/cli/trust_gate.rb,
lib/rubino/documents/html.rb,
lib/rubino/jobs/scheduler.rb,
lib/rubino/memory/backend.rb,
lib/rubino/memory/flusher.rb,
lib/rubino/oauth/provider.rb,
lib/rubino/oauth/registry.rb,
lib/rubino/run/repository.rb,
lib/rubino/session/picker.rb,
lib/rubino/tools/registry.rb,
lib/rubino/ui/indented_io.rb,
lib/rubino/ui/live_region.rb,
lib/rubino/ui/paste_store.rb,
lib/rubino/util/hyperlink.rb,
lib/rubino/commands/loader.rb,
lib/rubino/config/defaults.rb,
lib/rubino/documents/table.rb,
lib/rubino/files/workspace.rb,
lib/rubino/llm/tool_bridge.rb,
lib/rubino/memory/backends.rb,
lib/rubino/run/event_store.rb,
lib/rubino/session/message.rb,
lib/rubino/skills/registry.rb,
lib/rubino/tools/edit_tool.rb,
lib/rubino/tools/glob_tool.rb,
lib/rubino/tools/grep_tool.rb,
lib/rubino/tools/read_tool.rb,
lib/rubino/tools/ruby_tool.rb,
lib/rubino/tools/task_tool.rb,
lib/rubino/tools/todo_tool.rb,
lib/rubino/ui/printer_base.rb,
lib/rubino/ui/stdout_proxy.rb,
lib/rubino/agent/definition.rb,
lib/rubino/cli/chat_command.rb,
lib/rubino/cli/jobs_command.rb,
lib/rubino/commands/command.rb,
lib/rubino/config/validator.rb,
lib/rubino/documents/limits.rb,
lib/rubino/memory/aux_retry.rb,
lib/rubino/security/sandbox.rb,
lib/rubino/session/exporter.rb,
lib/rubino/skills/installer.rb,
lib/rubino/tools/patch_tool.rb,
lib/rubino/tools/probe_tool.rb,
lib/rubino/tools/shell_tool.rb,
lib/rubino/tools/steer_tool.rb,
lib/rubino/tools/write_tool.rb,
lib/rubino/ui/escape_reader.rb,
lib/rubino/ui/input_history.rb,
lib/rubino/util/atomic_file.rb,
lib/rubino/util/spill_store.rb,
lib/rubino/boot/config_guard.rb,
lib/rubino/cli/setup_command.rb,
lib/rubino/cli/tools_command.rb,
lib/rubino/commands/executor.rb,
lib/rubino/database/migrator.rb,
lib/rubino/interaction/probe.rb,
lib/rubino/llm/fake_provider.rb,
lib/rubino/llm/model_catalog.rb,
lib/rubino/run/approval_gate.rb,
lib/rubino/run/gate_registry.rb,
lib/rubino/security/redactor.rb,
lib/rubino/skills/skill_tool.rb,
lib/rubino/tools/fuzzy_match.rb,
lib/rubino/tools/memory_tool.rb,
lib/rubino/tools/vision_tool.rb,
lib/rubino/ui/headless_trace.rb,
lib/rubino/ui/subagent_cards.rb,
lib/rubino/util/ignore_rules.rb,
lib/rubino/util/secrets_mask.rb,
lib/rubino/attachments/defang.rb,
lib/rubino/attachments/policy.rb,
lib/rubino/cli/config_command.rb,
lib/rubino/cli/doctor_command.rb,
lib/rubino/cli/memory_command.rb,
lib/rubino/cli/server_command.rb,
lib/rubino/cli/skills_command.rb,
lib/rubino/commands/built_ins.rb,
lib/rubino/context/compressor.rb,
lib/rubino/documents/registry.rb,
lib/rubino/interaction/events.rb,
lib/rubino/session/repository.rb,
lib/rubino/tools/read_tracker.rb,
lib/rubino/ui/bottom_composer.rb,
lib/rubino/ui/completion_menu.rb,
lib/rubino/agent/tool_executor.rb,
lib/rubino/api/middleware/auth.rb,
lib/rubino/boot/encryption_key.rb,
lib/rubino/cli/chat/bang_shell.rb,
lib/rubino/cli/session_command.rb,
lib/rubino/database/connection.rb,
lib/rubino/llm/adapter_factory.rb,
lib/rubino/llm/content_builder.rb,
lib/rubino/llm/scenario_loader.rb,
lib/rubino/memory/deduplicator.rb,
lib/rubino/memory/sqlite_graph.rb,
lib/rubino/security/url_safety.rb,
lib/rubino/skills/prompt_index.rb,
lib/rubino/tools/question_tool.rb,
lib/rubino/tools/webfetch_tool.rb,
lib/rubino/agent/agent_registry.rb,
lib/rubino/agent/backoff_policy.rb,
lib/rubino/agent/fallback_chain.rb,
lib/rubino/attachments/classify.rb,
lib/rubino/attachments/preamble.rb,
lib/rubino/cli/chat/image_inbox.rb,
lib/rubino/config/configuration.rb,
lib/rubino/context/token_budget.rb,
lib/rubino/llm/adapter_response.rb,
lib/rubino/llm/auxiliary_client.rb,
lib/rubino/llm/credential_check.rb,
lib/rubino/llm/error_classifier.rb,
lib/rubino/llm/ruby_llm_adapter.rb,
lib/rubino/llm/thinking_support.rb,
lib/rubino/mcp/mcp_tool_wrapper.rb,
lib/rubino/memory/salience_gate.rb,
lib/rubino/output/turn_recorder.rb,
lib/rubino/security/secret_path.rb,
lib/rubino/tools/shell_registry.rb,
lib/rubino/tools/subagent_probe.rb,
lib/rubino/tools/task_stop_tool.rb,
lib/rubino/tools/websearch_tool.rb,
lib/rubino/ui/completion_source.rb,
lib/rubino/ui/markdown_renderer.rb,
lib/rubino/ui/queued_indicators.rb,
lib/rubino/cli/onboarding_wizard.rb,
lib/rubino/commands/handlers/mcp.rb,
lib/rubino/interaction/event_bus.rb,
lib/rubino/interaction/lifecycle.rb,
lib/rubino/interaction/polishing.rb,
lib/rubino/jobs/webhook_delivery.rb,
lib/rubino/llm/provider_resolver.rb,
lib/rubino/llm/reasoning_manager.rb,
lib/rubino/llm/scenario_selector.rb,
lib/rubino/memory/threat_scanner.rb,
lib/rubino/oauth/provider/github.rb,
lib/rubino/oauth/provider/google.rb,
lib/rubino/oauth/token_encryptor.rb,
lib/rubino/session/summary_store.rb,
lib/rubino/tools/multi_edit_tool.rb,
lib/rubino/tools/shell_kill_tool.rb,
lib/rubino/tools/shell_tail_tool.rb,
lib/rubino/ui/streaming_markdown.rb,
lib/rubino/agent/iteration_budget.rb,
lib/rubino/commands/handlers/help.rb,
lib/rubino/commands/handlers/jobs.rb,
lib/rubino/compression/compressor.rb,
lib/rubino/config/reasoning_prefs.rb,
lib/rubino/context/file_discovery.rb,
lib/rubino/context/token_estimate.rb,
lib/rubino/documents/cap_exceeded.rb,
lib/rubino/memory/backends/sqlite.rb,
lib/rubino/tools/attach_file_tool.rb,
lib/rubino/tools/background_tasks.rb,
lib/rubino/tools/shell_input_tool.rb,
lib/rubino/tools/task_result_tool.rb,
lib/rubino/ui/composer/input_line.rb,
lib/rubino/agent/model_call_runner.rb,
lib/rubino/cli/chat/idle_card_host.rb,
lib/rubino/context/summary_builder.rb,
lib/rubino/interaction/image_input.rb,
lib/rubino/interaction/input_queue.rb,
lib/rubino/llm/inline_think_filter.rb,
lib/rubino/security/deny_persister.rb,
lib/rubino/security/hardline_guard.rb,
lib/rubino/security/prefix_deriver.rb,
lib/rubino/skills/state_repository.rb,
lib/rubino/tools/shell_output_tool.rb,
lib/rubino/ui/probe_wait_indicator.rb,
lib/rubino/agent/action_claim_guard.rb,
lib/rubino/agent/response_validator.rb,
lib/rubino/commands/handlers/agents.rb,
lib/rubino/commands/handlers/config.rb,
lib/rubino/commands/handlers/memory.rb,
lib/rubino/commands/handlers/skills.rb,
lib/rubino/commands/handlers/status.rb,
lib/rubino/context/message_boundary.rb,
lib/rubino/context/prompt_assembler.rb,
lib/rubino/documents/converters/csv.rb,
lib/rubino/documents/converters/pdf.rb,
lib/rubino/documents/converters/xml.rb,
lib/rubino/interaction/cancel_token.rb,
lib/rubino/jobs/cron_job_repository.rb,
lib/rubino/memory/sqlite_extraction.rb,
lib/rubino/output/result_serializer.rb,
lib/rubino/security/approval_policy.rb,
lib/rubino/security/pattern_matcher.rb,
lib/rubino/security/secret_detector.rb,
lib/rubino/tools/custom_tool_loader.rb,
lib/rubino/tools/custom_tool_loader.rb,
lib/rubino/agent/degenerate_recovery.rb,
lib/rubino/api/middleware/rate_limit.rb,
lib/rubino/cli/chat/session_resolver.rb,
lib/rubino/commands/handlers/display.rb,
lib/rubino/compression/line_skeleton.rb,
lib/rubino/context/project_languages.rb,
lib/rubino/documents/converters/docx.rb,
lib/rubino/documents/converters/html.rb,
lib/rubino/documents/converters/json.rb,
lib/rubino/documents/converters/pptx.rb,
lib/rubino/documents/converters/xlsx.rb,
lib/rubino/llm/bedrock_bearer_client.rb,
lib/rubino/run/attachment_downloader.rb,
lib/rubino/tools/session_search_tool.rb,
lib/rubino/tools/summarize_file_tool.rb,
lib/rubino/api/middleware/json_parser.rb,
lib/rubino/attachments/classification.rb,
lib/rubino/commands/handlers/sessions.rb,
lib/rubino/compression/content_router.rb,
lib/rubino/compression/log_compressor.rb,
lib/rubino/context/tool_result_pruner.rb,
lib/rubino/documents/converters/plain.rb,
lib/rubino/run/session_approval_cache.rb,
lib/rubino/security/command_allowlist.rb,
lib/rubino/security/readonly_commands.rb,
lib/rubino/tools/read_attachment_tool.rb,
lib/rubino/tools/retrieve_output_tool.rb,
lib/rubino/tools/tool_call_repository.rb,
lib/rubino/ui/composer/subagent_panel.rb,
lib/rubino/cli/chat/completion_builder.rb,
lib/rubino/compression/diff_compressor.rb,
lib/rubino/compression/json_compressor.rb,
lib/rubino/context/tool_pair_sanitizer.rb,
lib/rubino/interaction/clipboard_image.rb,
lib/rubino/oauth/connection_repository.rb,
lib/rubino/output/headless_block_latch.rb,
lib/rubino/security/command_normalizer.rb,
lib/rubino/security/dangerous_patterns.rb,
lib/rubino/security/doom_loop_detector.rb,
lib/rubino/api/middleware/error_handler.rb,
lib/rubino/api/middleware/observability.rb,
lib/rubino/security/allowlist_persister.rb,
lib/rubino/agent/truncation_continuation.rb,
lib/rubino/compression/tsx_code_skeleton.rb,
lib/rubino/context/environment_inspector.rb,
lib/rubino/commands/handlers/agent_switch.rb,
lib/rubino/compression/compression_result.rb,
lib/rubino/compression/ruby_code_skeleton.rb,
lib/rubino/api/operations/health_operation.rb,
lib/rubino/api/operations/oauth/serializer.rb,
lib/rubino/api/operations/tasks/serializer.rb,
lib/rubino/jobs/handlers/distill_skill_job.rb,
lib/rubino/llm/cache_breakpoint_middleware.rb,
lib/rubino/memory/sqlite_extraction_prompt.rb,
lib/rubino/api/operations/metrics_operation.rb,
lib/rubino/compression/python_code_skeleton.rb,
lib/rubino/jobs/handlers/extract_memory_job.rb,
lib/rubino/jobs/handlers/compact_session_job.rb,
lib/rubino/api/operations/mode/show_operation.rb,
lib/rubino/api/operations/runs/stop_operation.rb,
lib/rubino/jobs/handlers/cleanup_sessions_job.rb,
lib/rubino/api/operations/files/read_operation.rb,
lib/rubino/api/operations/tasks/show_operation.rb,
lib/rubino/api/operations/tasks/stop_operation.rb,
lib/rubino/jobs/handlers/summarize_session_job.rb,
lib/rubino/api/operations/mode/update_operation.rb,
lib/rubino/api/operations/models/list_operation.rb,
lib/rubino/api/operations/runs/create_operation.rb,
lib/rubino/api/operations/runs/events_operation.rb,
lib/rubino/api/operations/skills/list_operation.rb,
lib/rubino/api/operations/tasks/index_operation.rb,
lib/rubino/compression/javascript_code_skeleton.rb,
lib/rubino/compression/typescript_code_skeleton.rb,
lib/rubino/api/operations/files/upload_operation.rb,
lib/rubino/api/operations/memory/index_operation.rb,
lib/rubino/api/operations/memory/stats_operation.rb,
lib/rubino/compression/tree_sitter_code_skeleton.rb,
lib/rubino/api/operations/memory/delete_operation.rb,
lib/rubino/api/operations/sessions/show_operation.rb,
lib/rubino/api/operations/sessions/undo_operation.rb,
lib/rubino/api/operations/skills/toggle_operation.rb,
lib/rubino/api/operations/cron_jobs/list_operation.rb,
lib/rubino/api/operations/cron_jobs/show_operation.rb,
lib/rubino/api/operations/sessions/index_operation.rb,
lib/rubino/api/operations/sessions/retry_operation.rb,
lib/rubino/api/operations/cron_jobs/pause_operation.rb,
lib/rubino/api/operations/sessions/create_operation.rb,
lib/rubino/api/operations/sessions/delete_operation.rb,
lib/rubino/api/operations/approvals/decide_operation.rb,
lib/rubino/api/operations/cron_jobs/create_operation.rb,
lib/rubino/api/operations/cron_jobs/delete_operation.rb,
lib/rubino/api/operations/cron_jobs/resume_operation.rb,
lib/rubino/api/operations/cron_jobs/update_operation.rb,
lib/rubino/api/operations/cron_jobs/trigger_operation.rb,
lib/rubino/api/operations/cron_jobs/schedule_validation.rb,
lib/rubino/api/operations/oauth/providers/list_operation.rb,
lib/rubino/api/operations/clarifications/decide_operation.rb,
lib/rubino/api/operations/oauth/connections/list_operation.rb,
lib/rubino/api/operations/oauth/providers/connect_operation.rb,
lib/rubino/api/operations/oauth/providers/callback_operation.rb,
lib/rubino/api/operations/oauth/connections/disconnect_operation.rb
Overview
Module-level DSL method for defining custom tools
Defined Under Namespace
Modules: API, ActiveAgent, ActiveSkill, Agent, Attachments, Boot, CLI, Commands, Compression, Config, Context, Database, Documents, Files, Interaction, Jobs, LLM, MCP, Memory, Metrics, Modes, OAuth, Output, Run, Security, Session, Skills, Tools, Trust, UI, UpdateCheck, Util, Workspace Classes: AmbiguousSessionError, CompactionError, ConfigurationError, ConflictError, DatabaseError, EmptyModelResponseError, Error, Interrupted, JobError, Logger, NotFoundError, PayloadTooLargeError, SessionError, StreamInterruptedError, ToolError, UnauthorizedError, UpstreamError, ValidationError
Constant Summary collapse
- VERSION =
"0.5.1"- TAGLINE =
The ONE product tagline (#559). Both chrome surfaces that introduce rubino —the ‘rubino –help` banner (CLI::Commands::TAGLINE) and the first-run chat welcome (Commands::Executor#show_welcome) — render this single string, so the two no longer drift into two different one-liners.
"rubino — an AI coding agent that reads, edits, and runs code."
Class Attribute Summary collapse
-
.agent_registry ⇒ Object
Returns the shared agent registry (primary/subagent/utility definitions).
-
.logger ⇒ Object
Returns the current structured logger.
-
.ui ⇒ Object
Returns the current UI adapter instance.
Class Method Summary collapse
-
.active_event_bus ⇒ Object
The EventBus of the CURRENTLY-RUNNING parent turn.
-
.aux_cancel_token ⇒ Object
The CancelToken governing best-effort AUX work (post-turn polishing: memory-extract / skill-distill / summarize) running on THIS thread, if any.
-
.background_sink ⇒ Object
The InputQueue of the CURRENTLY-RUNNING parent turn, if any.
-
.clean_errno_message(message) ⇒ Object
A clean, user-facing form of an Errno message.
-
.configuration ⇒ Object
Returns the current configuration instance.
-
.configure {|configuration| ... } ⇒ Object
Yields the configuration for block-style setup.
-
.current_subagent_id ⇒ Object
The BackgroundTasks entry id of the subagent run executing on THIS thread, if any.
-
.database ⇒ Object
Returns the database connection.
-
.database_repair_message ⇒ Object
A clean, actionable message when the on-disk DB is PRESENT but UNUSABLE, else nil.
- .define_tool ⇒ Object
-
.ensure_database_ready! ⇒ Object
First-run guard for any DB-touching entry point.
-
.ensure_directories! ⇒ Object
Ensures the home directory and subdirectories exist.
-
.event_bus ⇒ Object
Returns the event bus instance.
-
.headless? ⇒ Boolean
True while a HEADLESS one-shot run (‘rubino prompt`/-q) is executing on THIS thread.
-
.home_path ⇒ Object
Returns the home directory path.
-
.home_writable? ⇒ Boolean
Cheap, side-effect-free check that the home directory accepts writes — the F14 read-only-mount guard.
-
.loader ⇒ Object
Returns the Zeitwerk loader for autoloading.
-
.migration_lock_path ⇒ Object
Path to the inter-process migration lockfile in the rubino home.
-
.not_writable_error?(error) ⇒ Boolean
True when
erroris a write-permission / read-only-filesystem failure (vs. a genuinely un-set-up or transiently-busy home): a directory mounted read-only, owned by another user, or a SQLite “readonly database” report. -
.reload_configuration! ⇒ Object
Drops the memoized configuration so the next #configuration reload reads config.yml / .env fresh.
-
.reset! ⇒ Object
Resets all memoized state (useful for testing).
-
.reset_database! ⇒ Object
Drops the memoized DB connection so the next #database call opens the file afresh.
-
.with_aux_cancel_token(token) ⇒ Object
Binds
tokenas the aux cancel token for the duration of the block (set by Interaction::Polishing around its detached job drain, exactly like #with_ui binds the run’s UI). -
.with_background_sink(queue) ⇒ Object
Binds
queueas the background-subagent notification sink for the duration of the block (set by Interaction::Lifecycle around the turn, exactly like #with_ui binds the run’s UI). -
.with_current_subagent_id(id) ⇒ Object
Binds
idas the current subagent id for the duration of the block (set by TaskTool around the child run, exactly like #with_ui / the background sink). -
.with_event_bus(bus) ⇒ Object
Binds
busas the turn-scoped event bus for the duration of the block (set by Interaction::Lifecycle around the loop run, like #with_ui binds the UI). -
.with_headless ⇒ Object
Binds the headless one-shot flag for the duration of the block (set by ChatCommand#run_oneshot around the turn, exactly like #with_ui).
-
.with_ui(adapter) ⇒ Object
Runs the block with
adapteras the thread-scoped UI, restoring the previous value afterwards (nested-safe). -
.write_jail_db_hint ⇒ Object
When the home/DB is read-only AND sits OUTSIDE the OS write-jail, the cause is almost always a nested ‘rubino` launched from inside the agent’s own jailed shell tool: the shell is confined away from ~/.rubino, so it can’t write the session DB.
Class Attribute Details
.agent_registry ⇒ Object
Returns the shared agent registry (primary/subagent/utility definitions). Memoized process-wide so the ‘task` tool can resolve a subagent by name at call time without each boot path having to thread an instance through the tool executor. Both entry points (CLI ChatCommand, API ServerCommand) touch this at boot so delegation works identically over /v1 and in chat; the tool also reads it lazily here, so a stripped boot still resolves.
437 438 439 |
# File 'lib/rubino.rb', line 437 def agent_registry @agent_registry ||= Agent::AgentRegistry.new end |
.logger ⇒ Object
Returns the current structured logger.
253 254 255 |
# File 'lib/rubino.rb', line 253 def logger @logger ||= Logger.new end |
.ui ⇒ Object
Returns the current UI adapter instance.
A thread-local override (set via #with_ui) wins over the process-global adapter. This is what lets the API server run many runs concurrently: each run executes in its own thread (Run::Executor#start) with its own gated UI::API, and tools that reach for the global adapter (QuestionTool#ask, TaskTool) resolve to THAT run’s UI — not a shared, gate-less global that would silently drop interactive prompts (the clarify/‘question` flow) and could cross-talk between runs.
119 120 121 |
# File 'lib/rubino.rb', line 119 def ui Thread.current[:rubino_ui] || (@ui ||= UI.build(configuration.dig("ui", "adapter"))) end |
Class Method Details
.active_event_bus ⇒ Object
The EventBus of the CURRENTLY-RUNNING parent turn. The API/server path injects a fresh per-run bus (Run::Executor) that its Recorder is attached to; the CLI path uses the process-global bus. A backgrounded ‘task` subagent emits its SPAWNED/COMPLETED/FAILED lifecycle events here so they reach THAT run’s recorder (and SSE stream) rather than a detached global bus. Falls back to the global bus when no turn-scoped bus is bound.
144 145 146 |
# File 'lib/rubino.rb', line 144 def active_event_bus Thread.current[:rubino_event_bus] || event_bus end |
.aux_cancel_token ⇒ Object
The CancelToken governing best-effort AUX work (post-turn polishing: memory-extract / skill-distill / summarize) running on THIS thread, if any. The detached polishing thread (Interaction::Polishing) binds its token here so the aux retry/backoff loop (Memory::AuxRetry) can poll it and abort the moment the user presses Esc — without threading a token through every aux call site. Nil on the foreground turn thread and on the API/server path (no detached polishing), where aux work is uncancellable as before.
213 214 215 |
# File 'lib/rubino.rb', line 213 def aux_cancel_token Thread.current[:rubino_aux_cancel_token] end |
.background_sink ⇒ Object
The InputQueue of the CURRENTLY-RUNNING parent turn, if any. A background subagent (TaskTool) reads this to deliver its completion notification back into the parent’s live loop — the parent picks it up at its next iteration boundary via Loop#inject_steered_input, so the notice lands as a user message between turns, NEVER between an assistant tool_use and its results. Nil on the API/server path (no steering queue) — there the result is still reachable via the BackgroundTasks registry / ‘task_result`.
166 167 168 |
# File 'lib/rubino.rb', line 166 def background_sink Thread.current[:rubino_background_sink] end |
.clean_errno_message(message) ⇒ Object
A clean, user-facing form of an Errno message. Ruby appends an internal ‘ @ <syscall> - <path>` artifact to SystemCallError messages (e.g. “Operation not permitted @ apply2files - /home/x”, “Permission denied @ dir_s_mkdir - /home/x”) — the C function name and a path we already name elsewhere in the sentence. Strip that tail so the surfaced message is just the plain reason (“Operation not permitted”).
393 394 395 |
# File 'lib/rubino.rb', line 393 def () .to_s.sub(/ @ \S+ - .*\z/, "") end |
.configuration ⇒ Object
Returns the current configuration instance
92 93 94 |
# File 'lib/rubino.rb', line 92 def configuration @configuration ||= Config::Configuration.new end |
.configure {|configuration| ... } ⇒ Object
Yields the configuration for block-style setup
97 98 99 100 |
# File 'lib/rubino.rb', line 97 def configure yield(configuration) if block_given? configuration end |
.current_subagent_id ⇒ Object
The BackgroundTasks entry id of the subagent run executing on THIS thread, if any. Set by TaskTool#run_child_thread around the child Runner#run! so a tool the child invokes (today: ask_parent) can find its own registry entry — the card it surfaces on, the steer queue it receives answers through —without threading the id through the loop/executor/tool signatures. Nil on the parent thread and on any non-delegated (top-level) run, which is the signal ask_parent uses to refuse (a top-level agent has no parent to ask).
189 190 191 |
# File 'lib/rubino.rb', line 189 def current_subagent_id Thread.current[:rubino_current_subagent_id] end |
.database ⇒ Object
Returns the database connection
261 262 263 |
# File 'lib/rubino.rb', line 261 def database @database ||= Database::Connection.new(configuration.database_path) end |
.database_repair_message ⇒ Object
A clean, actionable message when the on-disk DB is PRESENT but UNUSABLE, else nil. Covers the un-setup-able state a user command must never crash on with a raw backtrace (#333/#359): a corrupt/malformed image →quarantine + recreate via setup. (The concurrent first-boot race that used to leave duplicate ‘schema_info` rows is now prevented at the source by the flock + side-effect-free `up_to_date?` fast path in the migrator, so there is no post-hoc duplicate-row state left to message about.) Read-only: never creates the file (matches doctor’s #68 contract).
411 412 413 414 415 416 417 418 419 420 421 422 423 424 |
# File 'lib/rubino.rb', line 411 def db = database return nil if db.memory? || !File.exist?(db.db_path) if db.corrupt? "database is corrupt (malformed image): #{db.db_path}\n" \ "Run `rubino doctor` to diagnose, then `rubino setup` to quarantine it " \ "and recreate a fresh database." end rescue StandardError # Detection itself must never crash a command; treat an unexpected probe # failure as "no clean message available" and let normal flow continue. nil end |
.define_tool ⇒ Object
112 113 114 115 116 117 118 |
# File 'lib/rubino/tools/custom_tool_loader.rb', line 112 def self.define_tool(&) builder = Tools::CustomToolBuilder.new builder.instance_eval(&) tool = builder.build Tools::Registry.register(tool) tool end |
.ensure_database_ready! ⇒ Object
First-run guard for any DB-touching entry point. A brand-new RUBINO_HOME has no schema yet (setup/chat hasn’t migrated it), so a read path like ‘rubino sessions list` would otherwise hit a raw `SQLite3::SQLException: no such table` backtrace (#35). `healthy?` only runs `SELECT 1`, which passes the moment SQLite lazily creates the empty file — the tables are still missing — so we also check migrator.pending?. Migrations are idempotent, so this is safe to call on every command. This is the same logic the interactive `chat` command already used; promoted here so the read CLIs (sessions/memory/jobs) share one implementation. Returns true when the schema is ready, false when initialization failed (callers decide whether that’s fatal or degrades to an empty state).
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/rubino.rb', line 284 def ensure_database_ready! connection = database migrator = Database::Migrator.new(connection) # FAST PATH (lock-free, race-safe): a side-effect-free read of # `schema_info` that does NOT construct a Sequel migrator. The common case # — an already-set-up home — returns here without touching the lock. Note # we MUST NOT call `migrator.pending?` off the lock: merely constructing # Sequel's IntegerMigrator inserts the version-0 row, and two concurrent # boots both inserting it is exactly the duplicate-row corruption (#race). if connection.healthy? && migrator.up_to_date? # A fully-migrated home can still be MOUNTED read-only (F14): the schema # reads fine, but the very next write (the session row) would crash with # a raw `SQLite3::ReadOnlyException` past this guard. Catch it HERE — a # cheap dir-writability probe, no DB write — and raise the accurate # "not writable" diagnosis instead, matching the migrate-path branch # below. A real (writable) home passes through untouched. unless home_writable? raise ConfigurationError, "rubino home / database is not writable: #{home_path}#{write_jail_db_hint}" end return true end ensure_directories! # Serialize the migration across concurrent boots (#race): N fresh # `rubino` processes on a brand-new home would otherwise BOTH probe + # migrate at once and corrupt the migrator bookkeeping. migrate! takes an # exclusive flock and does the `pending?` probe + migrate entirely under # it; waiters re-check and no-op. The lockfile lives in the home, which # ensure_directories! just created. migrator.migrate!(lock_path: migration_lock_path) true rescue Database::BusyError, ConfigurationError # A sustained concurrent-migration lock that outlived the connection # retry budget (#333/#359), or a careless RUBINO_HOME that points at a file # / a read-only parent (F13), is NOT an "un-set-up" home — re-raise so the # single CLI chokepoint surfaces the clean one-liner instead of this method # masking it as `false` → a misleading "run setup" message. raise rescue StandardError => e logger.debug(event: "ensure_database_ready_failed", error: "#{e.class}: #{e.}") # A read-only / not-writable home (F14) is NOT an un-set-up install: the # files may be perfectly present, the directory is just mounted read-only # or owned by another user, so migrate! can't open the lock/journal # (Errno::EACCES/EROFS) or SQLite reports "attempt to write a readonly # database". Masking that as `false` produced the misleading # "isn't set up yet — run `rubino setup`" — doctor already diagnoses it # correctly. Raise the ACCURATE diagnosis (matching the F13 home-error # phrasing) so the single CLI chokepoint surfaces it instead of "not # set up". Everything else still degrades to false. if not_writable_error?(e) raise ConfigurationError, "rubino home / database is not writable: #{home_path} " \ "(#{(e.)})#{write_jail_db_hint}" end false end |
.ensure_directories! ⇒ Object
Ensures the home directory and subdirectories exist. The home holds secrets (.env) and the database, so it is forced to 0700 here — the single code path every entry point (setup/chat/prompt/doctor) goes through to materialize the home — not just when ‘setup` ran first (#65): an auto-created home used to be left at the umask’s 0755.
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 |
# File 'lib/rubino.rb', line 468 def ensure_directories! home = home_path # A careless RUBINO_HOME (the value points at an EXISTING FILE, or its # parent is read-only) made FileUtils.mkdir_p raise a raw Errno::EEXIST / # Errno::EACCES backtrace from deep in fileutils.rb — masking the actual, # trivially-fixable mistake (F13). Normalize both into a clean, actionable # domain error AT THE SOURCE so the single CLI chokepoint surfaces one line # ("RUBINO_HOME is not a writable directory: <path>") + exit 1, in any # output format, with no trace. A directory that already exists is fine. if File.exist?(home) && !File.directory?(home) raise ConfigurationError, "RUBINO_HOME is not a writable directory: #{home} " \ "(it points at an existing file — set RUBINO_HOME to a directory path)" end begin FileUtils.mkdir_p(home) # chmod/mkdir on an EXISTING read-only RUBINO_HOME (its parent let # mkdir_p no-op, but the dir itself is not owner-writable) raises a raw # Errno::EPERM/EACCES from deep in fileutils — the same unguarded # backtrace F13 normalized for mkdir. Keep the perm ops inside the # rescue so a non-writable home yields the SAME clean one-line domain # error + exit 1, no trace. File.chmod(0o700, home) %w[memories sessions logs skills commands tools].each do |subdir| dir = File.join(home, subdir) FileUtils.mkdir_p(dir) unless File.directory?(dir) end rescue SystemCallError => e raise ConfigurationError, "RUBINO_HOME is not a writable directory: #{home} (#{(e.)})" end end |
.event_bus ⇒ Object
Returns the event bus instance
427 428 429 |
# File 'lib/rubino.rb', line 427 def event_bus @event_bus ||= Interaction::EventBus.new end |
.headless? ⇒ Boolean
True while a HEADLESS one-shot run (‘rubino prompt`/-q) is executing on THIS thread. Bound by ChatCommand#run_oneshot via #with_headless so tools that behave differently with no live REPL can tell — today only TaskTool, which forces `task` subagents to run FOREGROUND in headless mode (#380): in one-shot there is no IdleCardHost to fold a background child’s result back in and the process exits the instant the parent’s answer is ready, so a background fan-out would be silently dropped. Nil/false on the interactive REPL and the API/server path, where background subagents are surfaced.
237 238 239 |
# File 'lib/rubino.rb', line 237 def headless? Thread.current[:rubino_headless] || false end |
.home_path ⇒ Object
Returns the home directory path. Delegates to the SAME resolver the config Loader uses (RUBINO_HOME → else ~/.rubino) so the server (which loads config.yml through the Loader) and the CLI (config/setup/ doctor + ensure_directories!) never disagree about where state lives. Previously this read the YAML ‘paths.home` default (~/.rubino) and ignored $RUBINO_HOME, splitting the brain at first boot / for .env.
459 460 461 |
# File 'lib/rubino.rb', line 459 def home_path Rubino::Config::Loader.default_home_path end |
.home_writable? ⇒ Boolean
Cheap, side-effect-free check that the home directory accepts writes — the F14 read-only-mount guard. Falls back to assuming writable on any probe hiccup (the migrate path will still catch a real failure with the same accurate message), so this never wrongly blocks a usable home.
368 369 370 371 372 |
# File 'lib/rubino.rb', line 368 def home_writable? File.writable?(home_path) rescue StandardError true end |
.loader ⇒ Object
Returns the Zeitwerk loader for autoloading
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/rubino.rb', line 50 def loader @loader ||= begin loader = Zeitwerk::Loader.for_gem loader.inflector.inflect( # Acronym modules "cli" => "CLI", "llm" => "LLM", "ui" => "UI", "api" => "API", "tls" => "TLS", "mcp" => "MCP", "oauth" => "OAuth", # Files with compound names that need exact mapping "ruby_llm_adapter" => "RubyLLMAdapter", "mcp_tool_wrapper" => "MCPToolWrapper", "bedrock_bearer_client" => "BedrockBearerClient", "adapter_response" => "AdapterResponse", "indented_io" => "IndentedIO", "webfetch_tool" => "WebFetchTool", "websearch_tool" => "WebSearchTool", "skill_tool" => "SkillTool", "custom_tool_loader" => "CustomToolLoader", "custom_tool_builder" => "CustomToolBuilder", "tool_pair_sanitizer" => "ToolPairSanitizer", "degenerate_recovery" => "DegenerateResponseRecovery" ) # Migrations are plain SQL files, not Ruby constants loader.ignore( File.("rubino/database/migrations", __dir__) ) # errors.rb defines multiple constants in Rubino (NotFoundError, ...), # not a single Rubino::Errors module — loaded manually via require_relative. loader.ignore(File.("rubino/errors.rb", __dir__)) # rubino-agent.rb is a require shim matching the gem name; it maps to no # Rubino constant (and "Rubino-agent" isn't a valid cname). Zeitwerk must # not try to manage it. loader.ignore(File.("rubino-agent.rb", __dir__)) loader end end |
.migration_lock_path ⇒ Object
Path to the inter-process migration lockfile in the rubino home. A single source of truth so setup and the boot path lock on the SAME file.
399 400 401 |
# File 'lib/rubino.rb', line 399 def migration_lock_path File.join(home_path, ".migrate.lock") end |
.not_writable_error?(error) ⇒ Boolean
True when error is a write-permission / read-only-filesystem failure (vs. a genuinely un-set-up or transiently-busy home): a directory mounted read-only, owned by another user, or a SQLite “readonly database” report. Used by ensure_database_ready! to give an ACCURATE message instead of the misleading “not set up” (F14).
379 380 381 382 383 384 385 |
# File 'lib/rubino.rb', line 379 def not_writable_error?(error) return true if error.is_a?(Errno::EACCES) || error.is_a?(Errno::EROFS) || error.is_a?(Errno::EPERM) error..to_s.downcase.include?("readonly") || error..to_s.downcase.include?("read-only") || error..to_s.downcase.include?("read only") end |
.reload_configuration! ⇒ Object
Drops the memoized configuration so the next #configuration reload reads config.yml / .env fresh. Used after the first-run onboarding wizard writes them mid-process so the just-saved key is visible without a restart.
105 106 107 108 |
# File 'lib/rubino.rb', line 105 def reload_configuration! @configuration = nil configuration end |
.reset! ⇒ Object
Resets all memoized state (useful for testing)
445 446 447 448 449 450 451 |
# File 'lib/rubino.rb', line 445 def reset! @configuration = nil @ui = nil @database = nil @event_bus = nil @agent_registry = nil end |
.reset_database! ⇒ Object
Drops the memoized DB connection so the next #database call opens the file afresh. Used by ‘setup` after quarantining a corrupt DB so it reconnects to the newly-recreated file rather than the closed/renamed handle.
268 269 270 271 |
# File 'lib/rubino.rb', line 268 def reset_database! @database&.close @database = nil end |
.with_aux_cancel_token(token) ⇒ Object
Binds token as the aux cancel token for the duration of the block (set by Interaction::Polishing around its detached job drain, exactly like #with_ui binds the run’s UI). Thread-local so the aux retry loop reaches it with zero signature churn.
221 222 223 224 225 226 227 |
# File 'lib/rubino.rb', line 221 def with_aux_cancel_token(token) prev = Thread.current[:rubino_aux_cancel_token] Thread.current[:rubino_aux_cancel_token] = token yield ensure Thread.current[:rubino_aux_cancel_token] = prev end |
.with_background_sink(queue) ⇒ Object
Binds queue as the background-subagent notification sink for the duration of the block (set by Interaction::Lifecycle around the turn, exactly like #with_ui binds the run’s UI). Thread-local so a tool can reach it with zero signature churn through the loop/executor.
174 175 176 177 178 179 180 |
# File 'lib/rubino.rb', line 174 def with_background_sink(queue) prev = Thread.current[:rubino_background_sink] Thread.current[:rubino_background_sink] = queue yield ensure Thread.current[:rubino_background_sink] = prev end |
.with_current_subagent_id(id) ⇒ Object
Binds id as the current subagent id for the duration of the block (set by TaskTool around the child run, exactly like #with_ui / the background sink). Thread-local so the child’s tools reach it with zero signature churn.
197 198 199 200 201 202 203 |
# File 'lib/rubino.rb', line 197 def with_current_subagent_id(id) prev = Thread.current[:rubino_current_subagent_id] Thread.current[:rubino_current_subagent_id] = id yield ensure Thread.current[:rubino_current_subagent_id] = prev end |
.with_event_bus(bus) ⇒ Object
Binds bus as the turn-scoped event bus for the duration of the block (set by Interaction::Lifecycle around the loop run, like #with_ui binds the UI). Thread-local so a tool reaches it with no signature churn.
151 152 153 154 155 156 157 |
# File 'lib/rubino.rb', line 151 def with_event_bus(bus) prev = Thread.current[:rubino_event_bus] Thread.current[:rubino_event_bus] = bus yield ensure Thread.current[:rubino_event_bus] = prev end |
.with_headless ⇒ Object
Binds the headless one-shot flag for the duration of the block (set by ChatCommand#run_oneshot around the turn, exactly like #with_ui). Thread- local so a tool reaches it with zero signature churn through the loop.
244 245 246 247 248 249 250 |
# File 'lib/rubino.rb', line 244 def with_headless prev = Thread.current[:rubino_headless] Thread.current[:rubino_headless] = true yield ensure Thread.current[:rubino_headless] = prev end |
.with_ui(adapter) ⇒ Object
Runs the block with adapter as the thread-scoped UI, restoring the previous value afterwards (nested-safe). Used by Run::Executor to bind the run’s gated UI::API for the duration of the worker thread so global ‘Rubino.ui` lookups inside tools hit the right, gated instance.
130 131 132 133 134 135 136 |
# File 'lib/rubino.rb', line 130 def with_ui(adapter) prev = Thread.current[:rubino_ui] Thread.current[:rubino_ui] = adapter yield ensure Thread.current[:rubino_ui] = prev end |
.write_jail_db_hint ⇒ Object
When the home/DB is read-only AND sits OUTSIDE the OS write-jail, the cause is almost always a nested ‘rubino` launched from inside the agent’s own jailed shell tool: the shell is confined away from ~/.rubino, so it can’t write the session DB. Reuse the #74 write-jail framing so a bare “not writable” becomes attributable (#Y2A). Empty string (no extra hint) unless the jail is PROVEN enforcing and the home is outside its writable roots; best-effort, never raises into the boot path.
352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/rubino.rb', line 352 def write_jail_db_hint return "" unless defined?(Security::Sandbox) && Security::Sandbox.respond_to?(:enforcing?) return "" unless Security::Sandbox.enforcing? return "" if Security::Sandbox.writable?(home_path) " — #{home_path} is outside the workspace write-jail, so a nested rubino " \ "launched from inside the agent's shell tool can't write the session DB " \ "(tools.sandbox). Run rubino outside the jailed shell." rescue StandardError "" end |