rubino-agent 0.4.0 → 0.5.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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +11 -2
  3. data/AGENTS.md +1 -1
  4. data/CHANGELOG.md +137 -1
  5. data/CONTRIBUTING.md +10 -1
  6. data/README.md +14 -5
  7. data/Rakefile +31 -0
  8. data/docs/agents.md +42 -23
  9. data/docs/architecture.md +2 -2
  10. data/docs/commands.md +28 -1
  11. data/docs/configuration.md +20 -23
  12. data/docs/getting-started.md +5 -3
  13. data/docs/security.md +16 -5
  14. data/docs/troubleshooting.md +1 -1
  15. data/exe/rubino +16 -2
  16. data/install.sh +715 -54
  17. data/lib/rubino/active_agent.rb +73 -0
  18. data/lib/rubino/agent/action_claim_guard.rb +881 -0
  19. data/lib/rubino/agent/agent_registry.rb +5 -2
  20. data/lib/rubino/agent/definition.rb +1 -9
  21. data/lib/rubino/agent/fallback_chain.rb +0 -6
  22. data/lib/rubino/agent/iteration_budget.rb +109 -3
  23. data/lib/rubino/agent/loop.rb +476 -20
  24. data/lib/rubino/agent/model_call_runner.rb +81 -3
  25. data/lib/rubino/agent/prompts/build.txt +22 -5
  26. data/lib/rubino/agent/response_validator.rb +8 -0
  27. data/lib/rubino/agent/runner.rb +133 -8
  28. data/lib/rubino/agent/tool_executor.rb +166 -14
  29. data/lib/rubino/agent/truncation_continuation.rb +4 -1
  30. data/lib/rubino/api/server.rb +19 -0
  31. data/lib/rubino/boot/config_guard.rb +71 -0
  32. data/lib/rubino/cli/chat/completion_builder.rb +42 -6
  33. data/lib/rubino/cli/chat/idle_card_host.rb +7 -1
  34. data/lib/rubino/cli/chat/session_resolver.rb +87 -21
  35. data/lib/rubino/cli/chat_command.rb +1189 -50
  36. data/lib/rubino/cli/commands.rb +281 -1
  37. data/lib/rubino/cli/config_command.rb +68 -8
  38. data/lib/rubino/cli/doctor_command.rb +204 -12
  39. data/lib/rubino/cli/jobs_command.rb +12 -0
  40. data/lib/rubino/cli/memory_command.rb +53 -20
  41. data/lib/rubino/cli/onboarding_wizard.rb +79 -6
  42. data/lib/rubino/cli/session_command.rb +172 -18
  43. data/lib/rubino/cli/setup_command.rb +131 -8
  44. data/lib/rubino/cli/skills_command.rb +67 -20
  45. data/lib/rubino/cli/trust_gate.rb +16 -7
  46. data/lib/rubino/commands/built_ins.rb +2 -0
  47. data/lib/rubino/commands/command.rb +12 -2
  48. data/lib/rubino/commands/executor.rb +149 -12
  49. data/lib/rubino/commands/handlers/agent_switch.rb +100 -0
  50. data/lib/rubino/commands/handlers/agents.rb +133 -38
  51. data/lib/rubino/commands/handlers/config.rb +4 -1
  52. data/lib/rubino/commands/handlers/help.rb +113 -14
  53. data/lib/rubino/commands/handlers/memory.rb +15 -5
  54. data/lib/rubino/commands/handlers/sessions.rb +26 -3
  55. data/lib/rubino/commands/handlers/status.rb +9 -4
  56. data/lib/rubino/commands/loader.rb +12 -0
  57. data/lib/rubino/config/configuration.rb +86 -24
  58. data/lib/rubino/config/defaults.rb +140 -33
  59. data/lib/rubino/config/loader.rb +62 -12
  60. data/lib/rubino/config/validator.rb +341 -0
  61. data/lib/rubino/config/writer.rb +123 -31
  62. data/lib/rubino/context/compressor.rb +184 -22
  63. data/lib/rubino/context/message_boundary.rb +27 -1
  64. data/lib/rubino/context/project_languages.rb +90 -0
  65. data/lib/rubino/context/prompt_assembler.rb +104 -21
  66. data/lib/rubino/context/summary_builder.rb +45 -4
  67. data/lib/rubino/context/token_budget.rb +36 -11
  68. data/lib/rubino/context/token_estimate.rb +45 -0
  69. data/lib/rubino/context/tool_result_pruner.rb +81 -0
  70. data/lib/rubino/database/connection.rb +154 -3
  71. data/lib/rubino/database/migrations/001_create_initial_schema.rb +314 -40
  72. data/lib/rubino/database/migrator.rb +98 -5
  73. data/lib/rubino/documents/cap_exceeded.rb +13 -0
  74. data/lib/rubino/documents/converters/csv.rb +4 -3
  75. data/lib/rubino/documents/converters/docx.rb +29 -5
  76. data/lib/rubino/documents/converters/html.rb +5 -1
  77. data/lib/rubino/documents/converters/json.rb +2 -1
  78. data/lib/rubino/documents/converters/pdf.rb +11 -2
  79. data/lib/rubino/documents/converters/plain.rb +2 -1
  80. data/lib/rubino/documents/converters/pptx.rb +11 -2
  81. data/lib/rubino/documents/converters/xlsx.rb +35 -4
  82. data/lib/rubino/documents/converters/xml.rb +2 -1
  83. data/lib/rubino/documents/limits.rb +210 -0
  84. data/lib/rubino/documents.rb +10 -3
  85. data/lib/rubino/errors.rb +36 -5
  86. data/lib/rubino/interaction/cancel_token.rb +19 -3
  87. data/lib/rubino/interaction/events.rb +13 -0
  88. data/lib/rubino/interaction/lifecycle.rb +99 -13
  89. data/lib/rubino/interaction/polishing.rb +176 -0
  90. data/lib/rubino/jobs/cron_job_repository.rb +5 -8
  91. data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +11 -0
  92. data/lib/rubino/jobs/handlers/distill_skill_job.rb +65 -9
  93. data/lib/rubino/jobs/queue.rb +63 -8
  94. data/lib/rubino/jobs/runner.rb +24 -6
  95. data/lib/rubino/jobs/worker.rb +0 -4
  96. data/lib/rubino/llm/adapter_response.rb +47 -4
  97. data/lib/rubino/llm/credential_check.rb +15 -16
  98. data/lib/rubino/llm/error_classifier.rb +89 -1
  99. data/lib/rubino/llm/inline_think_filter.rb +69 -12
  100. data/lib/rubino/llm/request.rb +30 -3
  101. data/lib/rubino/llm/ruby_llm_adapter.rb +394 -46
  102. data/lib/rubino/llm/tool_bridge.rb +113 -9
  103. data/lib/rubino/mcp/manager.rb +18 -1
  104. data/lib/rubino/mcp/mcp_tool_wrapper.rb +14 -3
  105. data/lib/rubino/memory/aux_retry.rb +107 -0
  106. data/lib/rubino/memory/backends/sqlite.rb +73 -44
  107. data/lib/rubino/memory/backends.rb +23 -7
  108. data/lib/rubino/memory/salience_gate.rb +103 -0
  109. data/lib/rubino/memory/sqlite_extraction.rb +70 -0
  110. data/lib/rubino/memory/sqlite_extraction_prompt.rb +11 -0
  111. data/lib/rubino/memory/store.rb +33 -5
  112. data/lib/rubino/memory/threat_scanner.rb +52 -0
  113. data/lib/rubino/output/cost.rb +52 -0
  114. data/lib/rubino/output/headless_block_latch.rb +53 -0
  115. data/lib/rubino/output/result_serializer.rb +222 -0
  116. data/lib/rubino/output/turn_recorder.rb +77 -0
  117. data/lib/rubino/security/approval_policy.rb +227 -32
  118. data/lib/rubino/security/command_allowlist.rb +79 -4
  119. data/lib/rubino/security/doom_loop_detector.rb +21 -2
  120. data/lib/rubino/security/hardline_guard.rb +189 -16
  121. data/lib/rubino/security/pattern_matcher.rb +28 -5
  122. data/lib/rubino/security/prefix_deriver.rb +25 -6
  123. data/lib/rubino/security/readonly_commands.rb +145 -5
  124. data/lib/rubino/security/secret_path.rb +134 -0
  125. data/lib/rubino/security/url_safety.rb +255 -0
  126. data/lib/rubino/session/repository.rb +212 -11
  127. data/lib/rubino/session/store.rb +139 -14
  128. data/lib/rubino/skills/installer.rb +116 -32
  129. data/lib/rubino/skills/prompt_index.rb +2 -2
  130. data/lib/rubino/skills/registry.rb +42 -1
  131. data/lib/rubino/skills/skill.rb +63 -2
  132. data/lib/rubino/skills/skill_tool.rb +16 -5
  133. data/lib/rubino/tools/background_tasks.rb +122 -9
  134. data/lib/rubino/tools/base.rb +204 -3
  135. data/lib/rubino/tools/edit_tool.rb +73 -18
  136. data/lib/rubino/tools/glob_tool.rb +48 -9
  137. data/lib/rubino/tools/grep_tool.rb +103 -9
  138. data/lib/rubino/tools/multi_edit_tool.rb +64 -9
  139. data/lib/rubino/tools/patch_tool.rb +5 -0
  140. data/lib/rubino/tools/read_attachment_tool.rb +3 -1
  141. data/lib/rubino/tools/read_tool.rb +33 -15
  142. data/lib/rubino/tools/read_tracker.rb +153 -35
  143. data/lib/rubino/tools/registry.rb +113 -12
  144. data/lib/rubino/tools/result.rb +9 -1
  145. data/lib/rubino/tools/ruby_tool.rb +0 -0
  146. data/lib/rubino/tools/shell_registry.rb +70 -0
  147. data/lib/rubino/tools/shell_tool.rb +40 -1
  148. data/lib/rubino/tools/summarize_file_tool.rb +6 -0
  149. data/lib/rubino/tools/task_stop_tool.rb +10 -16
  150. data/lib/rubino/tools/task_tool.rb +36 -8
  151. data/lib/rubino/tools/vision_tool.rb +5 -0
  152. data/lib/rubino/tools/webfetch_tool.rb +39 -7
  153. data/lib/rubino/tools/websearch_tool.rb +92 -30
  154. data/lib/rubino/tools/write_tool.rb +23 -4
  155. data/lib/rubino/ui/api.rb +10 -1
  156. data/lib/rubino/ui/base.rb +11 -0
  157. data/lib/rubino/ui/bottom_composer.rb +382 -74
  158. data/lib/rubino/ui/cli.rb +515 -83
  159. data/lib/rubino/ui/completion_menu.rb +11 -7
  160. data/lib/rubino/ui/headless_trace.rb +63 -0
  161. data/lib/rubino/ui/live_region.rb +70 -7
  162. data/lib/rubino/ui/markdown_renderer.rb +142 -7
  163. data/lib/rubino/ui/notifier.rb +0 -2
  164. data/lib/rubino/ui/null.rb +52 -5
  165. data/lib/rubino/ui/paste_store.rb +16 -2
  166. data/lib/rubino/ui/queued_indicators.rb +6 -1
  167. data/lib/rubino/ui/status_bar.rb +61 -7
  168. data/lib/rubino/ui/streaming_markdown.rb +59 -6
  169. data/lib/rubino/ui/subagent_view.rb +15 -1
  170. data/lib/rubino/ui/tool_label.rb +52 -0
  171. data/lib/rubino/update_check.rb +39 -4
  172. data/lib/rubino/util/atomic_file.rb +117 -0
  173. data/lib/rubino/util/ignore_rules.rb +120 -0
  174. data/lib/rubino/util/output.rb +229 -12
  175. data/lib/rubino/util/secrets_mask.rb +70 -7
  176. data/lib/rubino/util/spill_store.rb +153 -0
  177. data/lib/rubino/version.rb +1 -1
  178. data/lib/rubino/workspace.rb +9 -1
  179. data/lib/rubino.rb +191 -7
  180. data/rubino-agent.gemspec +1 -0
  181. data/skills/ruby-expert/SKILL.md +1 -0
  182. metadata +41 -12
  183. data/lib/rubino/agent/router.rb +0 -65
  184. data/lib/rubino/database/migrations/002_create_runs.rb +0 -45
  185. data/lib/rubino/database/migrations/003_create_skill_states.rb +0 -15
  186. data/lib/rubino/database/migrations/004_create_cron_jobs.rb +0 -36
  187. data/lib/rubino/database/migrations/005_create_oauth_connections.rb +0 -27
  188. data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +0 -34
  189. data/lib/rubino/database/migrations/007_create_messages_fts.rb +0 -59
  190. data/lib/rubino/database/migrations/008_create_memory_facts.rb +0 -75
  191. data/lib/rubino/database/migrations/009_create_memory_graph.rb +0 -55
  192. data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +0 -20
data/docs/security.md CHANGED
@@ -4,7 +4,7 @@ rubino runs real tools — shell, file writes, Ruby, git. The safety model is la
4
4
 
5
5
  ## Is it safe to enable shell?
6
6
 
7
- Yes. `tools.shell` is **on by default** because the agent ships to run inside an isolated VM where running commands is the whole point. Every command is still gated: by default `security.require_confirmation_for_shell` is `true`, so each shell command goes through an approval prompt, and a hardline floor blocks catastrophic commands regardless of any setting.
7
+ Yes. `tools.shell` is **on by default** because the agent ships to run inside an isolated VM where running commands is the whole point. Every command is still gated: by default `security.confirm_policy` is `dangerous_only`, so a command matching a dangerous pattern goes through an approval prompt (set it to `confirm_all` to prompt on every command), and a hardline floor blocks catastrophic commands regardless of any setting.
8
8
 
9
9
  ## The approval decision order
10
10
 
@@ -51,10 +51,12 @@ Actions: `allow`, `ask`, `deny`. A `deny` rule is a deny-class check and beats e
51
51
 
52
52
  ## Shell confirmation policy
53
53
 
54
- `security.confirm_policy` (with `security.require_confirmation_for_shell` as a legacy alias):
54
+ `security.confirm_policy`:
55
55
 
56
- - **`confirm_all`** (default; alias `true`) every shell command not otherwise allowed/denied prompts for approval.
57
- - **`dangerous_only`** (alias `false`) safe commands run unprompted; only commands matching a dangerous pattern prompt. The hardline floor and `permissions: deny` still run first, so this never weakens the floor.
56
+ - **`dangerous_only`** (default) — safe commands run unprompted; only commands matching a dangerous pattern prompt. The hardline floor and `permissions: deny` still run first, so this never weakens the floor.
57
+ - **`confirm_all`** — every shell command not otherwise allowed/denied prompts for approval.
58
+
59
+ (The old `security.require_confirmation_for_shell` key was **removed** — it is no longer honored. Use `security.confirm_policy`.)
58
60
 
59
61
  ## Command allowlist
60
62
 
@@ -65,11 +67,14 @@ security:
65
67
  command_allowlist:
66
68
  - "git status"
67
69
  - "git diff"
68
- - "bundle exec rspec"
69
70
  ```
70
71
 
71
72
  An **empty** allowlist pre-approves nothing — pre-approval is opt-in.
72
73
 
74
+ A matched entry pre-approves only its **read-only intent**, never a smuggled write/exec form: an allowlisted head can't carry an output/exec flag (`git diff --output FILE`, `sort -o FILE`, `find -exec/-delete`), a git **global** flag (`git -c alias.x='!cmd' x`, `git -c core.sshCommand=…`, `git -C dir`, `--exec-path`), or a mutating/code-loading git subcommand (`git apply`, `git am`, `git push`, hooks). Heads whose argument is itself a program (`awk`, `sed`, `perl`, `python`, `ruby`, `node`, `tar`, `tee`, `xargs`, shells) are **never** auto-approved even if allowlisted — they still prompt. The **shipped default** is read-only git only; test/build runners (`bundle exec rspec`, `rake`, `npm test`) are deliberately not shipped auto-approved because they load and execute arbitrary project code by design — add one explicitly only if you accept that.
75
+
76
+ > An allowlist is a **convenience** layer, not a security boundary. Per industry practice (Claude Code/Codex, GTFOBins) a deny/allow list of command strings cannot be exhaustive; the OS sandbox is the real floor. This layer is narrowed to close the default-config and bare-`git` RCEs, not to be relied on as the only barrier.
77
+
73
78
  ## Auto-allowed read-only commands
74
79
 
75
80
  A built-in allowlist layer (`Security::ReadonlyCommands`, **on by default**) lets provably read-only shell commands run without a prompt. It is evaluated at the same decision step as the command allowlist — *below* the hardline floor and `permissions: deny`, which always win, even for commands you add yourself.
@@ -99,6 +104,12 @@ approvals:
99
104
  - "docker ps" # multi-word entry matches those leading tokens
100
105
  ```
101
106
 
107
+ ## Headless / non-interactive approvals fail closed
108
+
109
+ A one-shot or scripted run (`rubino prompt`, `chat -q`, or any run with no TTY) has **no interactive session to approve from**, so it **fails closed**: a tool that would otherwise prompt — a write/edit, or a shell command **not** covered by your `permissions` / command allowlist / read-only auto-allow — is **blocked, not run**. A single-line `blocked: <tool> needs approval but no interactive session (use --yolo to allow, or allowlist it)` goes to stderr and the run exits **2**, so automation/CI fails loudly instead of silently skipping (or, worse, auto-executing) the action. Anything you already allowlisted, and every read-only command, still runs unprompted.
110
+
111
+ To opt back into full auto-execute, pass **`--yolo`**; **`--no-yolo`** forces fail-closed even if a yolo default was set. `--yolo` is honored **only** as a CLI flag — a project-local or persisted config can never grant it, so an untrusted checkout can't silently switch a scripted run into auto-execute. The hardline floor and explicit `permissions: deny` rules still apply under `--yolo`. (See [commands.md §Exit codes](commands.md#exit-codes-scripting-around-prompt--one-shot).)
112
+
102
113
  ## Deny/approve scope: once vs session
103
114
 
104
115
  At the approval prompt you can decide for just this call or for the rest of the session. Session approvals are remembered by a **prefix/pattern class**, not the raw command:
@@ -48,7 +48,7 @@ The sqlite backend ranks by direct content relevance (FTS5/BM25) first; graph/re
48
48
 
49
49
  ## The agent keeps asking to approve every shell command
50
50
 
51
- That's `security.require_confirmation_for_shell: true` (the default `confirm_all` policy). Options: approve for the session at the prompt, add prefixes to `security.command_allowlist`, switch to `dangerous_only` (`security.confirm_policy: dangerous_only`), or use `/mode yolo` / `--yolo` to skip prompts (the hardline floor and `permissions: deny` still apply). See [security.md](security.md).
51
+ That's the `confirm_all` policy (`security.confirm_policy: confirm_all`). The default is `dangerous_only` (only dangerous-pattern commands prompt). Options: switch back with `rubino config set security.confirm_policy dangerous_only`, approve for the session at the prompt, add prefixes to `security.command_allowlist`, or use `/mode yolo` / `--yolo` to skip prompts (the hardline floor and `permissions: deny` still apply). See [security.md](security.md).
52
52
 
53
53
  ## A command was denied even with `--yolo`
54
54
 
data/exe/rubino CHANGED
@@ -1,9 +1,23 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ # On a bare POSIX/C-locale system Ruby's default_external is US-ASCII, so any
5
+ # File.read of a UTF-8 prompt/template (em-dashes etc.) raises
6
+ # Encoding::CompatibilityError before the agent even boots (#273; same class as
7
+ # #250/#251). Pin the process to UTF-8 up front so NO read path can regress,
8
+ # regardless of the user's locale. Promoting to UTF-8 is always safe; only
9
+ # touch it when the locale left us on a non-UTF-8 default.
10
+ Encoding.default_external = Encoding::UTF_8 unless Encoding.default_external == Encoding::UTF_8
11
+ Encoding.default_internal = Encoding::UTF_8 unless Encoding.default_internal == Encoding::UTF_8
12
+
4
13
  require "rubino"
5
14
 
6
- # Load .env before anything else so ENV vars are available immediately
7
- Rubino::Config::Loader.new.load
15
+ # Load .env before anything else so ENV vars are available immediately.
16
+ # A malformed config.yml must NOT crash the process with a raw Ruby+Psych
17
+ # backtrace (CFG-1) — ConfigGuard turns it into a clean boot abort so every
18
+ # command (including `rubino doctor`) reports it gracefully.
19
+ # Pass ARGV so the config-issue warning is suppressed for the pure-meta
20
+ # commands (version/help) that never need a configured model.
21
+ Rubino::Boot::ConfigGuard.load!(argv: ARGV)
8
22
 
9
23
  Rubino::CLI::Commands.start(ARGV)