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.
Files changed (376) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +115 -0
  4. data/.rubocop_todo.yml +955 -0
  5. data/.ruby-version +1 -0
  6. data/AGENTS.md +97 -0
  7. data/CHANGELOG.md +344 -0
  8. data/CONTRIBUTING.md +69 -0
  9. data/LICENSE +21 -0
  10. data/README.md +200 -0
  11. data/Rakefile +8 -0
  12. data/docs/agents.md +190 -0
  13. data/docs/api/v1.md +414 -0
  14. data/docs/architecture.md +177 -0
  15. data/docs/commands.md +375 -0
  16. data/docs/configuration.md +590 -0
  17. data/docs/getting-started.md +143 -0
  18. data/docs/jobs.md +332 -0
  19. data/docs/mcp.md +128 -0
  20. data/docs/memory.md +98 -0
  21. data/docs/models-and-keys.md +173 -0
  22. data/docs/oauth-providers.md +145 -0
  23. data/docs/plugins.md +195 -0
  24. data/docs/security.md +145 -0
  25. data/docs/skills.md +322 -0
  26. data/docs/tools.md +395 -0
  27. data/docs/troubleshooting.md +73 -0
  28. data/exe/rubino +9 -0
  29. data/install.sh +275 -0
  30. data/lib/rubino/active_skill.rb +50 -0
  31. data/lib/rubino/agent/agent_registry.rb +120 -0
  32. data/lib/rubino/agent/backoff_policy.rb +116 -0
  33. data/lib/rubino/agent/definition.rb +128 -0
  34. data/lib/rubino/agent/degenerate_recovery.rb +271 -0
  35. data/lib/rubino/agent/fallback_chain.rb +194 -0
  36. data/lib/rubino/agent/iteration_budget.rb +50 -0
  37. data/lib/rubino/agent/loop.rb +617 -0
  38. data/lib/rubino/agent/model_call_runner.rb +383 -0
  39. data/lib/rubino/agent/prompts/build.txt +69 -0
  40. data/lib/rubino/agent/prompts/compaction.txt +20 -0
  41. data/lib/rubino/agent/prompts/explore.txt +19 -0
  42. data/lib/rubino/agent/prompts/general.txt +20 -0
  43. data/lib/rubino/agent/prompts/plan.txt +31 -0
  44. data/lib/rubino/agent/response_validator.rb +70 -0
  45. data/lib/rubino/agent/router.rb +65 -0
  46. data/lib/rubino/agent/runner.rb +195 -0
  47. data/lib/rubino/agent/tool_executor.rb +402 -0
  48. data/lib/rubino/agent/truncation_continuation.rb +137 -0
  49. data/lib/rubino/api/middleware/auth.rb +43 -0
  50. data/lib/rubino/api/middleware/error_handler.rb +65 -0
  51. data/lib/rubino/api/middleware/json_parser.rb +100 -0
  52. data/lib/rubino/api/middleware/observability.rb +59 -0
  53. data/lib/rubino/api/middleware/rate_limit.rb +136 -0
  54. data/lib/rubino/api/operations/approvals/decide_operation.rb +49 -0
  55. data/lib/rubino/api/operations/clarifications/decide_operation.rb +44 -0
  56. data/lib/rubino/api/operations/cron_jobs/create_operation.rb +46 -0
  57. data/lib/rubino/api/operations/cron_jobs/delete_operation.rb +36 -0
  58. data/lib/rubino/api/operations/cron_jobs/list_operation.rb +55 -0
  59. data/lib/rubino/api/operations/cron_jobs/pause_operation.rb +34 -0
  60. data/lib/rubino/api/operations/cron_jobs/resume_operation.rb +34 -0
  61. data/lib/rubino/api/operations/cron_jobs/schedule_validation.rb +30 -0
  62. data/lib/rubino/api/operations/cron_jobs/show_operation.rb +32 -0
  63. data/lib/rubino/api/operations/cron_jobs/trigger_operation.rb +38 -0
  64. data/lib/rubino/api/operations/cron_jobs/update_operation.rb +42 -0
  65. data/lib/rubino/api/operations/files/read_operation.rb +40 -0
  66. data/lib/rubino/api/operations/files/upload_operation.rb +175 -0
  67. data/lib/rubino/api/operations/health_operation.rb +46 -0
  68. data/lib/rubino/api/operations/memory/delete_operation.rb +32 -0
  69. data/lib/rubino/api/operations/memory/index_operation.rb +80 -0
  70. data/lib/rubino/api/operations/memory/stats_operation.rb +28 -0
  71. data/lib/rubino/api/operations/metrics_operation.rb +18 -0
  72. data/lib/rubino/api/operations/mode/show_operation.rb +29 -0
  73. data/lib/rubino/api/operations/mode/update_operation.rb +42 -0
  74. data/lib/rubino/api/operations/models/list_operation.rb +45 -0
  75. data/lib/rubino/api/operations/oauth/connections/disconnect_operation.rb +77 -0
  76. data/lib/rubino/api/operations/oauth/connections/list_operation.rb +36 -0
  77. data/lib/rubino/api/operations/oauth/providers/callback_operation.rb +82 -0
  78. data/lib/rubino/api/operations/oauth/providers/connect_operation.rb +44 -0
  79. data/lib/rubino/api/operations/oauth/providers/list_operation.rb +35 -0
  80. data/lib/rubino/api/operations/oauth/serializer.rb +21 -0
  81. data/lib/rubino/api/operations/runs/create_operation.rb +77 -0
  82. data/lib/rubino/api/operations/runs/events_operation.rb +195 -0
  83. data/lib/rubino/api/operations/runs/stop_operation.rb +34 -0
  84. data/lib/rubino/api/operations/sessions/create_operation.rb +46 -0
  85. data/lib/rubino/api/operations/sessions/delete_operation.rb +33 -0
  86. data/lib/rubino/api/operations/sessions/index_operation.rb +82 -0
  87. data/lib/rubino/api/operations/sessions/retry_operation.rb +45 -0
  88. data/lib/rubino/api/operations/sessions/show_operation.rb +59 -0
  89. data/lib/rubino/api/operations/sessions/undo_operation.rb +38 -0
  90. data/lib/rubino/api/operations/skills/list_operation.rb +34 -0
  91. data/lib/rubino/api/operations/skills/toggle_operation.rb +40 -0
  92. data/lib/rubino/api/operations/tasks/index_operation.rb +30 -0
  93. data/lib/rubino/api/operations/tasks/serializer.rb +60 -0
  94. data/lib/rubino/api/operations/tasks/show_operation.rb +33 -0
  95. data/lib/rubino/api/operations/tasks/stop_operation.rb +47 -0
  96. data/lib/rubino/api/request.rb +54 -0
  97. data/lib/rubino/api/responses.rb +64 -0
  98. data/lib/rubino/api/router.rb +72 -0
  99. data/lib/rubino/api/schemas.rb +103 -0
  100. data/lib/rubino/api/server.rb +102 -0
  101. data/lib/rubino/api/tls.rb +108 -0
  102. data/lib/rubino/attachments/classification.rb +16 -0
  103. data/lib/rubino/attachments/classify.rb +171 -0
  104. data/lib/rubino/attachments/defang.rb +47 -0
  105. data/lib/rubino/attachments/policy.rb +36 -0
  106. data/lib/rubino/attachments/preamble.rb +120 -0
  107. data/lib/rubino/boot/encryption_key.rb +32 -0
  108. data/lib/rubino/cli/chat/bang_shell.rb +257 -0
  109. data/lib/rubino/cli/chat/completion_builder.rb +290 -0
  110. data/lib/rubino/cli/chat/idle_card_host.rb +69 -0
  111. data/lib/rubino/cli/chat/image_inbox.rb +168 -0
  112. data/lib/rubino/cli/chat/session_resolver.rb +176 -0
  113. data/lib/rubino/cli/chat_command.rb +1674 -0
  114. data/lib/rubino/cli/commands.rb +250 -0
  115. data/lib/rubino/cli/config_command.rb +96 -0
  116. data/lib/rubino/cli/doctor_command.rb +251 -0
  117. data/lib/rubino/cli/jobs_command.rb +60 -0
  118. data/lib/rubino/cli/memory_command.rb +135 -0
  119. data/lib/rubino/cli/onboarding_wizard.rb +207 -0
  120. data/lib/rubino/cli/server_command.rb +139 -0
  121. data/lib/rubino/cli/session_command.rb +125 -0
  122. data/lib/rubino/cli/setup_command.rb +107 -0
  123. data/lib/rubino/cli/skills_command.rb +85 -0
  124. data/lib/rubino/cli/tools_command.rb +81 -0
  125. data/lib/rubino/cli/trust_gate.rb +71 -0
  126. data/lib/rubino/commands/built_ins.rb +46 -0
  127. data/lib/rubino/commands/command.rb +116 -0
  128. data/lib/rubino/commands/executor.rb +550 -0
  129. data/lib/rubino/commands/handlers/agents.rb +510 -0
  130. data/lib/rubino/commands/handlers/config.rb +88 -0
  131. data/lib/rubino/commands/handlers/help.rb +148 -0
  132. data/lib/rubino/commands/handlers/jobs.rb +71 -0
  133. data/lib/rubino/commands/handlers/mcp.rb +229 -0
  134. data/lib/rubino/commands/handlers/memory.rb +200 -0
  135. data/lib/rubino/commands/handlers/sessions.rb +207 -0
  136. data/lib/rubino/commands/handlers/skills.rb +195 -0
  137. data/lib/rubino/commands/handlers/status.rb +211 -0
  138. data/lib/rubino/commands/loader.rb +90 -0
  139. data/lib/rubino/config/configuration.rb +455 -0
  140. data/lib/rubino/config/defaults.rb +569 -0
  141. data/lib/rubino/config/loader.rb +115 -0
  142. data/lib/rubino/config/reasoning_prefs.rb +67 -0
  143. data/lib/rubino/config/writer.rb +72 -0
  144. data/lib/rubino/context/compressor.rb +149 -0
  145. data/lib/rubino/context/environment_inspector.rb +176 -0
  146. data/lib/rubino/context/file_discovery.rb +45 -0
  147. data/lib/rubino/context/message_boundary.rb +39 -0
  148. data/lib/rubino/context/prompt_assembler.rb +382 -0
  149. data/lib/rubino/context/summary_builder.rb +159 -0
  150. data/lib/rubino/context/token_budget.rb +68 -0
  151. data/lib/rubino/context/tool_pair_sanitizer.rb +70 -0
  152. data/lib/rubino/database/connection.rb +77 -0
  153. data/lib/rubino/database/migrations/001_create_initial_schema.rb +156 -0
  154. data/lib/rubino/database/migrations/002_create_runs.rb +45 -0
  155. data/lib/rubino/database/migrations/003_create_skill_states.rb +15 -0
  156. data/lib/rubino/database/migrations/004_create_cron_jobs.rb +36 -0
  157. data/lib/rubino/database/migrations/005_create_oauth_connections.rb +27 -0
  158. data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +34 -0
  159. data/lib/rubino/database/migrations/007_create_messages_fts.rb +59 -0
  160. data/lib/rubino/database/migrations/008_create_memory_facts.rb +75 -0
  161. data/lib/rubino/database/migrations/009_create_memory_graph.rb +55 -0
  162. data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +20 -0
  163. data/lib/rubino/database/migrator.rb +48 -0
  164. data/lib/rubino/documents/converters/csv.rb +79 -0
  165. data/lib/rubino/documents/converters/docx.rb +129 -0
  166. data/lib/rubino/documents/converters/html.rb +28 -0
  167. data/lib/rubino/documents/converters/json.rb +35 -0
  168. data/lib/rubino/documents/converters/pdf.rb +59 -0
  169. data/lib/rubino/documents/converters/plain.rb +68 -0
  170. data/lib/rubino/documents/converters/pptx.rb +64 -0
  171. data/lib/rubino/documents/converters/xlsx.rb +62 -0
  172. data/lib/rubino/documents/converters/xml.rb +45 -0
  173. data/lib/rubino/documents/html.rb +71 -0
  174. data/lib/rubino/documents/registry.rb +68 -0
  175. data/lib/rubino/documents/table.rb +63 -0
  176. data/lib/rubino/documents.rb +50 -0
  177. data/lib/rubino/errors.rb +119 -0
  178. data/lib/rubino/files/workspace.rb +93 -0
  179. data/lib/rubino/interaction/cancel_token.rb +43 -0
  180. data/lib/rubino/interaction/clipboard_image.rb +84 -0
  181. data/lib/rubino/interaction/event_bus.rb +48 -0
  182. data/lib/rubino/interaction/events.rb +101 -0
  183. data/lib/rubino/interaction/image_input.rb +127 -0
  184. data/lib/rubino/interaction/input_queue.rb +117 -0
  185. data/lib/rubino/interaction/lifecycle.rb +299 -0
  186. data/lib/rubino/interaction/probe.rb +65 -0
  187. data/lib/rubino/interaction/state.rb +56 -0
  188. data/lib/rubino/jobs/cron_job_repository.rb +75 -0
  189. data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +32 -0
  190. data/lib/rubino/jobs/handlers/compact_session_job.rb +21 -0
  191. data/lib/rubino/jobs/handlers/distill_skill_job.rb +186 -0
  192. data/lib/rubino/jobs/handlers/extract_memory_job.rb +37 -0
  193. data/lib/rubino/jobs/handlers/summarize_session_job.rb +21 -0
  194. data/lib/rubino/jobs/queue.rb +184 -0
  195. data/lib/rubino/jobs/registry.rb +45 -0
  196. data/lib/rubino/jobs/runner.rb +79 -0
  197. data/lib/rubino/jobs/scheduler.rb +138 -0
  198. data/lib/rubino/jobs/webhook_delivery.rb +225 -0
  199. data/lib/rubino/jobs/worker.rb +59 -0
  200. data/lib/rubino/llm/adapter_factory.rb +47 -0
  201. data/lib/rubino/llm/adapter_response.rb +65 -0
  202. data/lib/rubino/llm/auxiliary_client.rb +61 -0
  203. data/lib/rubino/llm/bedrock_bearer_client.rb +235 -0
  204. data/lib/rubino/llm/content_builder.rb +55 -0
  205. data/lib/rubino/llm/credential_check.rb +93 -0
  206. data/lib/rubino/llm/error_classifier.rb +364 -0
  207. data/lib/rubino/llm/fake_provider.rb +292 -0
  208. data/lib/rubino/llm/inline_think_filter.rb +58 -0
  209. data/lib/rubino/llm/model_catalog.rb +29 -0
  210. data/lib/rubino/llm/provider_resolver.rb +48 -0
  211. data/lib/rubino/llm/reasoning_manager.rb +100 -0
  212. data/lib/rubino/llm/request.rb +56 -0
  213. data/lib/rubino/llm/ruby_llm_adapter.rb +794 -0
  214. data/lib/rubino/llm/scenario_loader.rb +68 -0
  215. data/lib/rubino/llm/scenario_selector.rb +80 -0
  216. data/lib/rubino/llm/scenarios/agent-creates-cron-failure.yml +29 -0
  217. data/lib/rubino/llm/scenarios/agent-creates-cron.yml +36 -0
  218. data/lib/rubino/llm/scenarios/analysis.yml +501 -0
  219. data/lib/rubino/llm/scenarios/complex-analysis.yml +598 -0
  220. data/lib/rubino/llm/scenarios/failure.yml +65 -0
  221. data/lib/rubino/llm/scenarios/happy-path.yml +24 -0
  222. data/lib/rubino/llm/scenarios/provider-quota-completed.yml +14 -0
  223. data/lib/rubino/llm/scenarios/wide-table.yml +121 -0
  224. data/lib/rubino/llm/scenarios/with-approvals.yml +50 -0
  225. data/lib/rubino/llm/scenarios/with-artifacts.yml +98 -0
  226. data/lib/rubino/llm/scenarios/with-clarify.yml +32 -0
  227. data/lib/rubino/llm/scenarios/with-reasoning.yml +175 -0
  228. data/lib/rubino/llm/scenarios/with-uploads.yml +104 -0
  229. data/lib/rubino/llm/thinking_support.rb +84 -0
  230. data/lib/rubino/llm/tool_bridge.rb +89 -0
  231. data/lib/rubino/logger.rb +99 -0
  232. data/lib/rubino/mcp/manager.rb +180 -0
  233. data/lib/rubino/mcp/mcp_tool_wrapper.rb +69 -0
  234. data/lib/rubino/mcp.rb +57 -0
  235. data/lib/rubino/memory/backend.rb +104 -0
  236. data/lib/rubino/memory/backends/default.rb +101 -0
  237. data/lib/rubino/memory/backends/sqlite.rb +653 -0
  238. data/lib/rubino/memory/backends.rb +53 -0
  239. data/lib/rubino/memory/deduplicator.rb +74 -0
  240. data/lib/rubino/memory/extractor.rb +85 -0
  241. data/lib/rubino/memory/flusher.rb +31 -0
  242. data/lib/rubino/memory/retriever.rb +50 -0
  243. data/lib/rubino/memory/sqlite_extraction_prompt.rb +70 -0
  244. data/lib/rubino/memory/sqlite_graph.rb +154 -0
  245. data/lib/rubino/memory/store.rb +228 -0
  246. data/lib/rubino/memory/threat_scanner.rb +68 -0
  247. data/lib/rubino/metrics.rb +175 -0
  248. data/lib/rubino/modes.rb +93 -0
  249. data/lib/rubino/oauth/connection_repository.rb +95 -0
  250. data/lib/rubino/oauth/provider/github.rb +75 -0
  251. data/lib/rubino/oauth/provider/google.rb +59 -0
  252. data/lib/rubino/oauth/provider.rb +149 -0
  253. data/lib/rubino/oauth/registry.rb +86 -0
  254. data/lib/rubino/oauth/token_encryptor.rb +87 -0
  255. data/lib/rubino/plugins/registry.rb +75 -0
  256. data/lib/rubino/plugins.rb +86 -0
  257. data/lib/rubino/run/approval_gate.rb +243 -0
  258. data/lib/rubino/run/attachment_downloader.rb +166 -0
  259. data/lib/rubino/run/event_store.rb +74 -0
  260. data/lib/rubino/run/executor.rb +383 -0
  261. data/lib/rubino/run/gate_registry.rb +39 -0
  262. data/lib/rubino/run/recorder.rb +69 -0
  263. data/lib/rubino/run/repository.rb +118 -0
  264. data/lib/rubino/run/session_approval_cache.rb +118 -0
  265. data/lib/rubino/security/allowlist_persister.rb +55 -0
  266. data/lib/rubino/security/approval_policy.rb +227 -0
  267. data/lib/rubino/security/command_allowlist.rb +24 -0
  268. data/lib/rubino/security/dangerous_patterns.rb +118 -0
  269. data/lib/rubino/security/deny_persister.rb +73 -0
  270. data/lib/rubino/security/doom_loop_detector.rb +43 -0
  271. data/lib/rubino/security/hardline_guard.rb +105 -0
  272. data/lib/rubino/security/pattern_matcher.rb +62 -0
  273. data/lib/rubino/security/prefix_deriver.rb +124 -0
  274. data/lib/rubino/security/readonly_commands.rb +211 -0
  275. data/lib/rubino/session/exporter.rb +101 -0
  276. data/lib/rubino/session/message.rb +77 -0
  277. data/lib/rubino/session/repository.rb +295 -0
  278. data/lib/rubino/session/store.rb +198 -0
  279. data/lib/rubino/session/summary_store.rb +65 -0
  280. data/lib/rubino/skills/prompt_index.rb +85 -0
  281. data/lib/rubino/skills/registry.rb +208 -0
  282. data/lib/rubino/skills/skill.rb +176 -0
  283. data/lib/rubino/skills/skill_tool.rb +215 -0
  284. data/lib/rubino/skills/state_repository.rb +37 -0
  285. data/lib/rubino/skills/toggle.rb +26 -0
  286. data/lib/rubino/tools/answer_child_tool.rb +83 -0
  287. data/lib/rubino/tools/ask_parent_tool.rb +232 -0
  288. data/lib/rubino/tools/attach_file_tool.rb +120 -0
  289. data/lib/rubino/tools/background_tasks.rb +520 -0
  290. data/lib/rubino/tools/base.rb +222 -0
  291. data/lib/rubino/tools/custom_tool_loader.rb +119 -0
  292. data/lib/rubino/tools/edit_tool.rb +122 -0
  293. data/lib/rubino/tools/git_tool.rb +71 -0
  294. data/lib/rubino/tools/github_tool.rb +233 -0
  295. data/lib/rubino/tools/glob_tool.rb +69 -0
  296. data/lib/rubino/tools/grep_tool.rb +206 -0
  297. data/lib/rubino/tools/memory_tool.rb +184 -0
  298. data/lib/rubino/tools/multi_edit_tool.rb +110 -0
  299. data/lib/rubino/tools/patch_tool.rb +260 -0
  300. data/lib/rubino/tools/probe_tool.rb +175 -0
  301. data/lib/rubino/tools/question_tool.rb +128 -0
  302. data/lib/rubino/tools/read_attachment_tool.rb +180 -0
  303. data/lib/rubino/tools/read_tool.rb +212 -0
  304. data/lib/rubino/tools/read_tracker.rb +98 -0
  305. data/lib/rubino/tools/registry.rb +166 -0
  306. data/lib/rubino/tools/result.rb +113 -0
  307. data/lib/rubino/tools/ruby_tool.rb +0 -0
  308. data/lib/rubino/tools/session_search_tool.rb +103 -0
  309. data/lib/rubino/tools/shell_input_tool.rb +96 -0
  310. data/lib/rubino/tools/shell_kill_tool.rb +76 -0
  311. data/lib/rubino/tools/shell_output_tool.rb +72 -0
  312. data/lib/rubino/tools/shell_registry.rb +158 -0
  313. data/lib/rubino/tools/shell_tail_tool.rb +118 -0
  314. data/lib/rubino/tools/shell_tool.rb +330 -0
  315. data/lib/rubino/tools/steer_tool.rb +118 -0
  316. data/lib/rubino/tools/subagent_probe.rb +89 -0
  317. data/lib/rubino/tools/summarize_file_tool.rb +182 -0
  318. data/lib/rubino/tools/task_result_tool.rb +90 -0
  319. data/lib/rubino/tools/task_stop_tool.rb +80 -0
  320. data/lib/rubino/tools/task_tool.rb +622 -0
  321. data/lib/rubino/tools/test_tool.rb +454 -0
  322. data/lib/rubino/tools/todo_tool.rb +93 -0
  323. data/lib/rubino/tools/tool_call_repository.rb +33 -0
  324. data/lib/rubino/tools/vision_tool.rb +85 -0
  325. data/lib/rubino/tools/webfetch_tool.rb +153 -0
  326. data/lib/rubino/tools/websearch_tool.rb +179 -0
  327. data/lib/rubino/tools/write_tool.rb +61 -0
  328. data/lib/rubino/trust.rb +88 -0
  329. data/lib/rubino/ui/api.rb +296 -0
  330. data/lib/rubino/ui/base.rb +252 -0
  331. data/lib/rubino/ui/bottom_composer.rb +1599 -0
  332. data/lib/rubino/ui/cli.rb +1987 -0
  333. data/lib/rubino/ui/completion_menu.rb +321 -0
  334. data/lib/rubino/ui/completion_source.rb +284 -0
  335. data/lib/rubino/ui/escape_reader.rb +169 -0
  336. data/lib/rubino/ui/indented_io.rb +88 -0
  337. data/lib/rubino/ui/input_history.rb +108 -0
  338. data/lib/rubino/ui/live_region.rb +183 -0
  339. data/lib/rubino/ui/markdown_renderer.rb +506 -0
  340. data/lib/rubino/ui/notifier.rb +163 -0
  341. data/lib/rubino/ui/null.rb +195 -0
  342. data/lib/rubino/ui/paste_store.rb +176 -0
  343. data/lib/rubino/ui/printer_base.rb +79 -0
  344. data/lib/rubino/ui/probe_wait_indicator.rb +75 -0
  345. data/lib/rubino/ui/queued_indicators.rb +66 -0
  346. data/lib/rubino/ui/status_bar.rb +100 -0
  347. data/lib/rubino/ui/stdout_proxy.rb +161 -0
  348. data/lib/rubino/ui/streaming_markdown.rb +186 -0
  349. data/lib/rubino/ui/subagent_cards.rb +134 -0
  350. data/lib/rubino/ui/subagent_view.rb +255 -0
  351. data/lib/rubino/ui.rb +21 -0
  352. data/lib/rubino/update_check.rb +187 -0
  353. data/lib/rubino/util/duration.rb +23 -0
  354. data/lib/rubino/util/hyperlink.rb +105 -0
  355. data/lib/rubino/util/output.rb +145 -0
  356. data/lib/rubino/util/secrets_mask.rb +83 -0
  357. data/lib/rubino/version.rb +5 -0
  358. data/lib/rubino/workspace.rb +85 -0
  359. data/lib/rubino-agent.rb +5 -0
  360. data/lib/rubino.rb +318 -0
  361. data/mise.toml +2 -0
  362. data/rubino-agent.gemspec +103 -0
  363. data/skills/ruby-expert/SKILL.md +67 -0
  364. data/skills/ruby-expert/references/concurrency.md +357 -0
  365. data/skills/ruby-expert/references/datetime-and-encoding.md +363 -0
  366. data/skills/ruby-expert/references/errors-and-types.md +460 -0
  367. data/skills/ruby-expert/references/gem-authoring.md +459 -0
  368. data/skills/ruby-expert/references/language-idioms.md +465 -0
  369. data/skills/ruby-expert/references/metaprogramming.md +339 -0
  370. data/skills/ruby-expert/references/oo-design.md +553 -0
  371. data/skills/ruby-expert/references/performance.md +383 -0
  372. data/skills/ruby-expert/references/rails.md +424 -0
  373. data/skills/ruby-expert/references/security.md +404 -0
  374. data/skills/ruby-expert/references/testing.md +473 -0
  375. data/skills/ruby-expert/references/tooling.md +466 -0
  376. metadata +856 -0
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.3
data/AGENTS.md ADDED
@@ -0,0 +1,97 @@
1
+ # AGENTS.md — rubino
2
+
3
+ Onboarding for anyone (human or AI) working on this codebase.
4
+
5
+ ## Vision
6
+
7
+ `rubino` is a **micro agent**: a small, self-contained Ruby gem that runs an LLM-driven coding/automation agent *on the machine where the work happens* — the user's PC or a VM. You drop it onto that machine and it works there, reachable over a clean HTTP API and a CLI. It executes conversation turns with tool calls, streams events, manages skills, and persists sessions/memory in a single SQLite file. It can be embedded as a library, run as a CLI, or run as a small service behind a REST gateway.
8
+
9
+ It is a lightweight agent, **not a heavy framework**. We pick the few primitives that have real value and skip everything else. Scope discipline is part of the product.
10
+
11
+ ## Non-goals (explicit)
12
+
13
+ These decisions are permanent. Reopen only with a written justification.
14
+
15
+ - **No dual HTTP APIs.** One server, one versioned prefix (`/v1`). When we break, bump to `/v2`. Never aliases.
16
+ - **No multi-tenant inside one process.** One instance = one workspace = one identity. Multi-tenant deployments run N instances.
17
+ - **No dashboard / theme system / plugin hub.** The dashboard is whatever client consumes our API. We don't ship a UI server.
18
+ - **No skill catalog bundled.** Skills are user-supplied directories. We don't ship a catalog.
19
+ - **No compat layers.** Renamed something? Rename everywhere. Reshaped a response? Bump version.
20
+ - **No auto-update endpoints.** Deploy = gem update + restart. Period.
21
+ - **No backwards-compat shims, no _deprecated suffixes, no "legacy" anything.**
22
+
23
+ ## Architectural rules (load-bearing)
24
+
25
+ These are load-bearing. When a PR violates them, it is the PR that gets redone.
26
+
27
+ 1. **One interface per concept.** One `LLM::Adapter`, one `Tools::Base`, one `Memory::Store`. Variants are subclasses, not parallels.
28
+ 2. **No PORO commands in `lib/`.** Domain logic on models/repositories; orchestration as class methods. No `app/services/Foo::DoThingCommand`.
29
+ 3. **No optional-magic params.** Signatures explicit. If long, refactor responsibilities, don't add `**opts`.
30
+ 4. **Schema-validated input at boundaries** (HTTP, CLI, MCP). Internals trust types.
31
+ 5. **Typed errors only.** `raise NotFoundError, "session"` not `raise "session not found"`.
32
+ 6. **Test contracts, not implementation.** Internals change without breaking tests, not vice versa.
33
+ 7. **Structured logging from day one.** JSON-line. No `puts`/`pp` outside the UI layer. Logger DI-injected.
34
+ 8. **No global singletons.** Config flows from the main loop into everything via constructor.
35
+ 9. **Handlers stay thin.** Endpoint >30 LOC → extract `Operation` class.
36
+ 10. **Less code, less bugs.** A refactor is good if it deletes net lines. Distrust preparatory abstractions (PORO/Result/custom errors before you need them).
37
+
38
+ ## Tech stack
39
+
40
+ | Layer | Choice | Why |
41
+ |---|---|---|
42
+ | LLM | `ruby_llm ~> 1.0` (thin wrap) | One mature gem covering OpenAI/Anthropic/Gemini/Bedrock. We never call providers directly. |
43
+ | MCP | `ruby_llm-mcp ~> 0.8` | Pairs natively with `ruby_llm`. |
44
+ | HTTP server | `rack` + `puma` | Standard. WEBrick is gone. |
45
+ | DB | `sequel` + `sqlite3` | One file, zero ops. Postgres later if multi-writer matters. |
46
+ | Schema | `dry-schema` | Boundary validation. |
47
+ | Scheduler | `rufus-scheduler` | Cron syntax, in-process. |
48
+ | Config | `dry-configurable` | Already in use. |
49
+ | CLI | `thor` | Already in use. |
50
+ | Autoload | `zeitwerk` | Already in use. |
51
+ | CLI UI | `tty-*` + `reline` | The interactive `rubino chat` prompt (history, completion, multi-line editing). |
52
+
53
+ `ruby_llm` is the foundation. `LLM::RubyLLMAdapter` is a thin wrapper: it configures `RubyLLM`, delegates `chat`/`stream`/`model_info`, and exposes nothing the underlying gem can't already do. **If you find yourself adding business logic to the adapter, push it into the run loop instead.**
54
+
55
+ ## Layout
56
+
57
+ ```
58
+ lib/rubino/
59
+ agent/ # run loop, lifecycle, multi-agent routing
60
+ api/ # HTTP gateway (Rack app, middleware, operations)
61
+ cli/ # thor commands
62
+ config/ # dry-configurable
63
+ context/ # compaction, prompt assembly
64
+ interaction/ # conversation lifecycle primitives
65
+ jobs/ # scheduler + worker (cron)
66
+ llm/ # ruby_llm adapter + model registry
67
+ mcp/ # MCP client + tool wrapper
68
+ memory/ # Memory::Store + extractors + retrievers
69
+ oauth/ # OAuth providers + connection storage
70
+ security/ # approval policy, sandboxing
71
+ session/ # repository + persistence
72
+ skills/ # SKILL.md loader
73
+ tools/ # built-in tools
74
+ ui/ # cli/null/api stub UIs
75
+ ```
76
+
77
+ ## Surfaces this project exposes
78
+
79
+ - **HTTP API** (`/v1/*`) — the canonical interface. See `docs/api/v1.md`.
80
+ - **CLI** — `rubino {setup,chat,prompt,server,config,memory,sessions,jobs,tools,doctor,version}`.
81
+ - **Library** — `require "rubino"; Rubino.run(...)`.
82
+
83
+ The interactive CLI ships as part of `rubino chat`. Multi-agent routing, MCP, and plugin hooks are designed in but not fully wired yet.
84
+
85
+ ## Working on this codebase
86
+
87
+ - Read `docs/architecture.md` for the bigger picture.
88
+ - Read `docs/api/v1.md` for HTTP contract.
89
+ - Read `docs/oauth-providers.md` for OAuth design.
90
+ - Run `bundle exec rspec` before pushing.
91
+ - Don't commit if rubocop fails.
92
+
93
+ ## Git/commit conventions
94
+
95
+ - Conventional commits welcome but not required.
96
+ - No AI attribution lines in commit messages.
97
+ - Small commits over big ones. A commit that touches 30 files needs a justification.
data/CHANGELOG.md ADDED
@@ -0,0 +1,344 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added — CLI redesign & in-chat surfaces
6
+
7
+ A scroll-native `rubino chat` refresh plus several new slash commands and input affordances. All are documented under [docs/commands.md](docs/commands.md) and [docs/configuration.md](docs/configuration.md).
8
+
9
+ - **Rail input + status bar.** The chat input now leads with a red `▍` rail and a clean `❯` caret; a dim status bar pinned under the input shows the session mode (dim `default` / yellow `plan` / red `yolo`), the resolved model id, and context saturation. Configurable via `display.statusbar` (default on), `display.tool_output_preview_lines`, and `display.input_max_rows`.
10
+ - **File-backed paste pipeline.** A multi-line paste collapses to a `[Pasted text #N +M lines]` placeholder that expands on send; a very large paste overflows to `<home>/sessions/<id>/paste_N.txt` with a read-tool pointer. Tuned by `paste.collapse_lines` and `paste.file_threshold_tokens`.
11
+ - **`/model`** — show or switch the live session model (persists `model.default`, retargets the running session).
12
+ - **Context hygiene** — `/compact` (compact now), `/clear` (alias for `/new`), `/export [path]` (write the transcript as markdown).
13
+ - **`Esc Esc` rewind** — at the idle prompt, opens a picker over previous messages and forks the session before the chosen one, pre-filled for editing.
14
+ - **Notifications** (`notifications.*`) — attention signals (terminal bell / iTerm2 OSC 9 / optional `command` hook) on a long turn finishing, an approval prompt, or a blocked subagent.
15
+ - **Auto-allow read-only shell** (`approvals.auto_allow_readonly`, default on; `approvals.readonly_commands` to extend) — provably read-only commands (`ls`, `grep`, `git log`, …) run without a prompt, below the hardline floor and `permissions: deny`. See [docs/security.md](docs/security.md#auto-allowed-read-only-commands).
16
+ - **`!` bang prefix** — run a shell line yourself, no approval gate; output streams into the transcript and is injected so the next turn can act on it.
17
+ - **In-chat management surfaces** — `/mcp` (list/restart/disable MCP servers), `/jobs` (the persistent job queue), and `/config` (read/set effective config in the REPL).
18
+ - **Type-ahead while working** — Enter interrupts and runs your line next; Alt+Enter (or `/queued`) queues it after the current turn.
19
+
20
+ ### Fixed — approval-model safety (W3: #152 #144 #143 #147 #151)
21
+
22
+ - **Shift+Tab can no longer blind-cycle into yolo** (#152). The press that
23
+ lands on yolo only ARMS it and shows a confirm toast ("press shift+tab again
24
+ to confirm"); a deliberate second press confirms, a blind mash keeps
25
+ re-arming and never confirms. The toast counts running background subagents
26
+ whose approval gates would drop. An explicit `/mode yolo` stays direct but
27
+ now warns once when live children would start running gated actions
28
+ unprompted.
29
+ - **A background-task event can no longer auto-deny an open child approval
30
+ prompt** (#144). The `/agents <id>` `[o]nce/[a]lways/[n]o` prompt treats an
31
+ empty/aborted read as "ask again" (never as an answer); after repeated empty
32
+ reads it leaves the child parked instead of denying. Card repaints are also
33
+ suppressed while an interactive prompt owns the terminal, so a completion
34
+ fold-in can't paint over (or abort) the blocked read. Denying now requires
35
+ an explicit keypress.
36
+ - **Policy denials are no longer reported to the model as "denied by user"**
37
+ (#143). `Tools::Result.denied` now threads the deny reason: the hardline
38
+ floor, a `permissions: deny` rule and the doom-loop guard each get their own
39
+ message (all stating "not by the user"), and the doom-loop one nudges the
40
+ model to change strategy instead of retrying. Only a real human rejection
41
+ still reads "Tool execution denied by user."
42
+ - **Enter is no longer swallowed by the verb-suggestion dropdown on a complete
43
+ command** (#147). With the menu open, Enter submits when the typed token
44
+ already equals the (sole/selected) candidate — e.g. the exact
45
+ `/agents sa_xxx` the approval hint tells you to run — and when the argument
46
+ slot is empty (`/agents sa_xxx ` with the steer/probe/--stop menu open).
47
+ Arrow-navigating onto a candidate still makes Enter accept it.
48
+ - **read-before-edit is now enforced per SESSION, not per turn** (#151). The
49
+ `ReadTracker` is keyed on the session id, so an edit in a later turn no
50
+ longer forces a redundant re-read (and a second approval round-trip) of an
51
+ unchanged file; any on-disk mtime change still demands a fresh read, and a
52
+ resumed session in a new process still starts conservative.
53
+
54
+ ### Added — built-in `ruby-expert` skill
55
+
56
+ Rubino now ships a built-in **`ruby-expert`** skill so every install makes the
57
+ agent a Ruby/Rails expert out of the box — no setup or copy step.
58
+
59
+ - **New skill source: gem-bundled skills.** The skill registry now always scans
60
+ the gem's own `skills/` directory in addition to the user paths
61
+ (`.rubino/skills`, `~/.rubino/skills`). Built-ins are scanned **first**, so a
62
+ same-named user skill still overrides them. Toggle with the new
63
+ `skills.include_builtin` config key (default `true`).
64
+ - **The `ruby-expert` skill** (`skills/ruby-expert/`) is a router `SKILL.md` plus
65
+ twelve bundled references covering: language idioms, metaprogramming, OO design,
66
+ errors & type checking, concurrency, Rails, testing, performance, security,
67
+ tooling, gem authoring, and dates/times/encoding. The agent loads only the
68
+ reference a task needs (3-level progressive disclosure).
69
+
70
+ ### Changed — BREAKING: project renamed `ruby-agent` → Rubino
71
+
72
+ The project was rebranded from `ruby-agent` to **Rubino**. This is a clean break with **no backward-compatibility fallbacks** — the old names no longer work and must be updated everywhere they are referenced.
73
+
74
+ - **Gem name:** `ruby_agent` → `rubino-agent` (install with `gem install rubino-agent`). The bare `rubino` name on RubyGems is an unrelated parked gem and is intentionally not used; a thin `lib/rubino-agent.rb` shim lets `require "rubino-agent"` resolve to the canonical `require "rubino"`.
75
+ - **CLI command / executable:** `ruby-agent` → `rubino` (e.g. `rubino setup`, `rubino chat`, `rubino server`).
76
+ - **Ruby module namespace:** `RubyAgent` → `Rubino` (and `RubyAgent::VERSION` → `Rubino::VERSION`).
77
+ - **Config home directory:** `~/.ruby_agent` → `~/.rubino`. No fallback to the old path; move your existing data if you want to keep it.
78
+ - **SQLite database filename:** `ruby_agent.sqlite3` → `rubino.sqlite3` (under the resolved home).
79
+ - **Environment variables:** every `RUBY_AGENT_*` was renamed to `RUBINO_*`. No fallback reads the old names. Full list:
80
+ - `RUBY_AGENT_HOME` → `RUBINO_HOME`
81
+ - `RUBY_AGENT_ENCRYPTION_KEY` → `RUBINO_ENCRYPTION_KEY`
82
+ - `RUBY_AGENT_API_KEY` → `RUBINO_API_KEY`
83
+ - `RUBY_AGENT_API_HOST` → `RUBINO_API_HOST`
84
+ - `RUBY_AGENT_API_PORT` → `RUBINO_API_PORT`
85
+ - `RUBY_AGENT_TLS` → `RUBINO_TLS`
86
+ - `RUBY_AGENT_WEBHOOK_URL` → `RUBINO_WEBHOOK_URL`
87
+ - `RUBY_AGENT_WEBHOOK_SECRET` → `RUBINO_WEBHOOK_SECRET`
88
+ - `RUBY_AGENT_LOG_LEVEL` → `RUBINO_LOG_LEVEL`
89
+ - `RUBY_AGENT_LOG_FORMAT` → `RUBINO_LOG_FORMAT`
90
+ - `RUBY_AGENT_HYPERLINKS` → `RUBINO_HYPERLINKS`
91
+ - `RUBY_AGENT_ALLOW_FAKE` → `RUBINO_ALLOW_FAKE`
92
+ - `RUBY_AGENT_REAL_HOME` → `RUBINO_REAL_HOME`
93
+ - `RUBY_AGENT_GIT_REF` → `RUBINO_GIT_REF`
94
+ - `RUBY_AGENT_RUBY_VERSION` → `RUBINO_RUBY_VERSION`
95
+
96
+ The GitHub repository is `github.com/Jhonnyr97/rubino-agent`. Publishing the renamed gem is **not** done as part of this change.
97
+
98
+ ### Fixed
99
+
100
+ - **Invalid cron schedules can no longer brick the server** (#164): `POST/PATCH /v1/jobs` validates the cron string BEFORE persisting (422 with the canonical validation envelope, nothing committed), and `Jobs::Scheduler` skips + warns on a malformed persisted row instead of crashing boot — existing poisoned DBs recover on restart.
101
+ - **Invalid API keys fail fast** (#126): statusless provider auth rejections (e.g. MiniMax "login fail", "incorrect api key") are classified non-retryable AUTH, surfacing the actionable auth error in one round-trip instead of ~60-90s of silent retries.
102
+ - **`rubino chat --help` / `rubino prompt --help` print usage** (#134): a help flag on any top-level command is intercepted at dispatch and routed to Thor's help — no provider call, no memory writes.
103
+ - **`RUBINO_HOME` relocates skills** (#135): the stock `~/.rubino/skills` entry resolves against the resolved home (same resolver as config/.env/DB/commands), so isolated homes discover their skills.
104
+ - **Order-dependent suite abort** (#163): a spec leaking a pared-down tool registry is cleaned up, and the one-shot exit spec converts an unexpected `SystemExit` into a failing example instead of killing the rspec process.
105
+
106
+ ### Documentation
107
+
108
+ - `docs/api/v1.md` aligned to the real API surface (#165, #166, #167): SSE catalogue documents the non-streaming contract (no `message.delta`/`reasoning.delta`), the approval decision enum lists all seven accepted values with semantics, and `GET /v1/sessions`, `/v1/memory*`, `/v1/tasks*` are documented. A doc-drift spec locks the documented route list to the registered routes.
109
+
110
+ ## [0.3.0] - 2026-06-06
111
+
112
+ Major capability release: the core conversation loop was ported 1:1 from the reference implementation (formalized LLM boundary, retry/backoff/fallback, degenerate-response recovery), background subagents became the default delegation path, the memory subsystem grew a pluggable backend contract with a tiny-Zep SQLite backend that is now the default, CLI gained image/file input and a scroll-native redesign, and a reference-aligned approval model (hardline floor, dangerous-pattern deny, prefix-derived rules) landed. Consolidated from `feature/subagent-view` (#48) plus #49-#58.
113
+
114
+ ### Breaking / upgrade notes
115
+
116
+ - **Default memory backend is now SQLite (tiny-Zep).** `memory.backend` now defaults to `"sqlite"` (previously `"default"`). The new backend reads/writes the `:memory_facts` table; the old `"default"` backend used the `:memories` table. On upgrade, users who were on the previous `"default"` backend and do **not** pin `memory.backend: "default"` in their config will stop reading their prior memory store — the new backend looks only at `:memory_facts`. Your old data in `:memories` is **not deleted**, just no longer read. **No automatic backfill is shipped.** To keep old recall, pin `memory.backend: "default"` in config. (Acceptable for alpha; documented here.)
117
+
118
+ ### Added
119
+
120
+ #### Subagents & delegation
121
+ - Background subagents: the `task` tool is now background-by-default (Claude-Code-modeled), so a parent run delegates without blocking on the child (#50). Subagent delegation via the `task` tool is wired on both CLI and API.
122
+ - CLI live nested view of subagent activity (Phase 1).
123
+
124
+ #### CLI image & file input
125
+ - Headless image attachment for one-shot runs (`-q` / prompt) via `--image` and `@image` (#53).
126
+ - Interactive image input: `@image`, drag-drop path, and clipboard paste resolve to `image_paths` (#49). New `ImageInput` path; vision is served via the configured aux model.
127
+
128
+ #### Shell
129
+ - `shell_input` tool to answer interactive prompts of background shells over stdin, enabling interactive subprocesses (#52).
130
+
131
+ #### Memory
132
+ - Pluggable `Memory::Backend` contract + registry (mirrors `Tools::Registry`) and a `memory backend` command to select the active backend.
133
+ - tiny-Zep SQLite memory backend: LLM fact extraction, temporal tracking, and hybrid (FTS5 + best-effort vector) retrieval, with graph-lite entities/edges and 1-hop expansion.
134
+
135
+ #### Approvals (reference-aligned, S1-S7)
136
+ - Non-bypassable hardline deny floor (S1).
137
+ - `DangerousPatterns` with explicit deny-before-allow ordering (S2).
138
+ - `PrefixDeriver` + rule-keyed session approval cache (S3); a `:prefix` rule is only derived for the shell tool.
139
+ - `security.confirm_policy` (`confirm_all` default | `dangerous_only`) (S4).
140
+ - `/v1` enum + enriched approval payload + `always_prefix` persistence (S5).
141
+ - CLI scopes persist derived rules (prefix/command); `always_tool` stays CLI-only (S7).
142
+
143
+ #### Skills
144
+ - Directory-based skills with 3-level progressive disclosure and a registered `SkillTool` (A).
145
+ - Mandatory skill index injected into the system prompt (B).
146
+ - Registry honors `StateRepository` disable on both index and load (C).
147
+
148
+ #### Core loop port (1:1 from the reference)
149
+ - Formalized LLM boundary: normalized `Request` + `Response` (slice 1).
150
+ - `ResponseValidator` + empty-response retry (slice 2).
151
+ - `BackoffPolicy` + `ErrorClassifier` (unknown -> retryable) (slice 3).
152
+ - `ModelCallRunner` inner retry loop (slice 4).
153
+ - `DegenerateResponseRecovery` ladder (prefill-to-continue) (slice 5).
154
+ - `ReasoningManager` (thinking render + echo-back seam) (slice 6).
155
+ - `FallbackChain` (provider/model rotation, restore primary) (slice 7).
156
+ - Max-iterations toolless summary (slice 8).
157
+ - `TruncationContinuation` + dead-branch cleanup (slice 9).
158
+
159
+ #### CLI / UX
160
+ - Scroll-native visual redesign of `rubino chat` (M0 + M2).
161
+ - Bottom-pinned composer (visible input while the agent streams above); steering — type/inject messages mid-turn (queued for next loop boundary).
162
+ - Inline completion dropdown with arrow-key navigation, `@` file picker, and input token highlighting.
163
+ - Assistant markdown rendered while streaming (per-block); markdown tables fit terminal width; `--resume` replays assistant turns as markdown.
164
+ - Built-in fake LLM provider for tests/dev.
165
+ - Multi-arch (x86_64 + arm64) system release image build in CI.
166
+
167
+ ### Changed
168
+
169
+ - Memory recall quality: recency and graph signals are demoted to tail supplements so direct FTS/vector matches win and survive single-shot recall (#51).
170
+ - LLM: route MiniMax through the anthropic-compatible endpoint and drop the OpenAI-compat band-aid patches; harden MiniMax-M2.7 (empty-turn retry, unknown-error retry, thinking/temp/max_tokens, overload backoff); recover tool-call turns that close the stream without `[DONE]`.
171
+ - LLM: use ruby_llm `before_message`/`after_message` (`on_new`/`on_end` deprecated); resolve provider once.
172
+ - Human-in-loop: wait on approvals/clarify instead of failing; `shell` tool on by default.
173
+ - `question` tool combines prompt + options into a single `ui.ask` call.
174
+
175
+ ### Fixed
176
+
177
+ - **Approval gate**: bounded interruptible wait with auto-deny on expiry (24h -> 15min); an abandoned approval no longer parks a worker thread (which previously froze Puma) (#55, fixes #54). Earlier W1 fix also released the approval-parked worker on cancel/timeout, plus a skill-ref TOCTOU (W3).
178
+ - Interactive prompts work mid-turn (`run_in_terminal`); clarify/question no longer drop prompts on the API path; API accepts image-only runs (blank input + attachments).
179
+ - Per-run `EventBus` isolation (no cross-run event/output bleed); `run.completed` always carries final output on non-streaming runs.
180
+ - SSE idle watchdog no longer kills long silent tool calls.
181
+ - Local Ruby programming errors are classified non-retryable.
182
+ - First-run setup UX; `doctor` is provider-aware and reports migration/provider-key health correctly; tools listing and dropdown/help polish.
183
+ - CLI bughunt batch (B1-B8): reset cancel token each turn so Ctrl-C no longer poisons the session; single `ToolExecutor` sink counts/persists streaming tool results; errored tools render red; loose markdown lists stay one streamed block; `/skills` descriptions word-wrap. Plus Ctrl-C interrupt double-message dedupe, Escape dismisses slash autocomplete, real Available list on unknown command, multi-line paste/resize repaint, and markdown prose word-wrap/headings/table fixes.
184
+ - `grep` accepts a file path (not only a directory); File API workspace rooted at tool cwd so artifacts download.
185
+
186
+ ### Security
187
+
188
+ - Universal secure-by-default file attachment handling (#57): classify-by-magic in both directions, typed per-kind preambles, a no-multimodal warning, nonce-framed and defanged inline text, an `attachments.policy` knob, and a unified safety pipeline.
189
+ - SSRF guard always allows loopback hosts for the File API.
190
+
191
+ ## [0.2.19] - 2026-06-04
192
+
193
+ Codebase audit follow-up: tool/message integrity, internal-contract fixes, and dead-code removal. Net −1578 LOC. Researched against industry practice (Anthropic/OpenAI tool-pairing, Claude Code, Vercel AI SDK, LangChain, Cline, ruby_llm).
194
+
195
+ ### Fixed
196
+
197
+ #### Tool/message integrity
198
+ - Compaction (`Compressor#create_child_session`) and `Session::Forker` no longer drop assistant `metadata[:tool_calls]` / `token_count` when copying messages — strict providers (Anthropic/Bedrock) no longer 400 on orphaned tool pairs after resume. Shared `Session::Store#copy_into` does a faithful copy.
199
+ - `ToolPairSanitizer` predicate fixed: it now detects assistant tool calls via `metadata[:tool_calls]` (was checking `tool_call_id`, which assistant rows never carry — the guard was inert) and is id-aware (a paired trailing call is preserved; an unanswered one is trimmed).
200
+ - `PromptAssembler#build` runs a defensive pre-send tool-pair repair (mirrors Claude Code's pre-call sanitization), recovering sessions already corrupted by the above.
201
+ - `BedrockBearerClient#stream` emits the common chunk contract `{type:, text:, message_id:}` (via `InlineThinkFilter`, with `MESSAGE_COMPLETED` boundaries) and is wrapped in `with_retries`; the `chunk.is_a?(Hash)` fallbacks were removed from the UI now that all adapters are uniform.
202
+
203
+ #### Internal contracts
204
+ - `tools.web` / `tools.browser` now actually gate `webfetch`/`websearch`: tools declare their config gate via `Tools::Base#config_key` (single source of truth shared by the registry and the CLI), instead of name string-munging that never queried the shipped defaults. Closes a "web off but still on" security footgun. Removed the dead `tools.browser` key.
205
+ - `confirm(scope:)` is part of the UI contract on all adapters (`Base`/`CLI`/`Null`/`API`); interactive tool approvals no longer raise `ArgumentError: unknown keyword: :scope`.
206
+ - `RUBINO_HOME` is now the single source of truth for the home path (`config set/get`, `setup`, `doctor` and the server agree); resolution shared in `Config::Loader`.
207
+ - `run.attachments_downloaded` is only emitted when files were actually downloaded (no empty diagnostic event on plain chats).
208
+ - Defensive guard for upstream errors with a string-shaped `error` body (ruby_llm OpenAI streaming `parse_streaming_error`) — the real upstream message surfaces instead of a `TypeError: String does not have #dig method`.
209
+
210
+ ### Removed (dead code, audit-verified, −1578 LOC)
211
+ - LSP subsystem (`lsp/`), parallel `Auth` module (superseded by `oauth/`), `Terminal::Composer` (superseded by Reline `UI::LineInput`), `Session::Exporter`, `Memory::ProjectMemory`/`UserProfile` (duplicated `Memory::Retriever`), `Context::CompactionPolicy` (duplicated `TokenBudget`), `Security::RiskClassifier` (per-tool risk is canonical), `Session::Forker` (unused; real fork is the API `parent_session_id` path), `FileSystemTool` + its `file_system` arms, the `snapshots/` subsystem + CLI `undo`/`redo` + config, the unused Recorder live `Queue`, and assorted orphaned methods.
212
+ - Kept (planned features, currently dormant, to be wired later): MCP, multi-agent (Build/Plan/Explore), plugin hooks.
213
+
214
+ ## [0.1.0] - 2025-05-11
215
+
216
+ ### Added
217
+
218
+ #### Core
219
+ - Agent loop with iteration budget and tool execution
220
+ - Interaction lifecycle state machine with 14 states
221
+ - Event bus for decoupling core from UI
222
+ - SQLite database with WAL mode (Sequel migrations)
223
+ - Session persistence (messages, tool calls, events)
224
+ - Context compaction with session lineage
225
+ - Summary builder with structured template
226
+ - Token budget management
227
+ - Tool pair sanitizer for compaction integrity
228
+
229
+ #### Agents
230
+ - Multi-agent architecture (Build, Plan, Explore, General)
231
+ - Agent router with @mention support
232
+ - Per-agent model, tools, permissions, and MCP scoping
233
+ - Hidden utility agents (compaction, title)
234
+
235
+ #### Tools (15 built-in)
236
+ - file_system (read/write/list/exists)
237
+ - edit (exact string replacement)
238
+ - grep (ripgrep-backed regex search)
239
+ - glob (file pattern matching)
240
+ - git (status/diff/log/branch/show)
241
+ - github (PRs/issues/reviews via gh CLI or API)
242
+ - shell (command execution with allowlist)
243
+ - ruby (code evaluation)
244
+ - apply_patch (unified diff application)
245
+ - webfetch (URL content retrieval)
246
+ - websearch (Tavily/SearXNG/DuckDuckGo)
247
+ - question (interactive user queries)
248
+ - todowrite (task tracking)
249
+ - lsp (go_to_definition, references, hover, symbols, diagnostics)
250
+ - skill (on-demand skill loading)
251
+
252
+ #### MCP
253
+ - ruby_llm-mcp integration
254
+ - stdio, SSE, and streamable HTTP transports
255
+ - MCPToolWrapper for seamless tool registration
256
+ - Per-agent MCP server scoping
257
+ - OAuth 2.1 with PKCE for remote MCP servers
258
+
259
+ #### Memory
260
+ - Persistent memory store (7 kinds)
261
+ - Auto-extraction from conversations
262
+ - Jaccard similarity deduplication
263
+ - User profile and project memory
264
+ - Memory retriever with char limits
265
+ - Pre-compaction flush
266
+
267
+ #### Jobs
268
+ - SQLite-backed job queue
269
+ - Inline/manual/worker modes
270
+ - Retry with exponential backoff
271
+ - Job run auditing
272
+ - 5 built-in handlers (extract, summarize, compact, cleanup, index)
273
+
274
+ #### Security
275
+ - Pattern-based approval policy (allow/ask/deny)
276
+ - Wildcard matching on tool calls and paths
277
+ - Doom loop detection (3x identical calls)
278
+ - Command allowlist
279
+ - Risk classifier
280
+
281
+ #### Skills
282
+ - SKILL.md files with YAML frontmatter
283
+ - Multi-location discovery
284
+ - Lazy content loading
285
+ - SkillTool for agent access
286
+
287
+ #### Commands
288
+ - Custom slash commands from Markdown files
289
+ - $ARGUMENTS and positional params ($1-$9)
290
+ - Shell output injection (!`command`)
291
+ - File content injection (@path)
292
+ - Built-in: /help, /commands, /skills, /exit
293
+
294
+ #### Plugins
295
+ - 46 hook points across all subsystems
296
+ - File-based plugin loading from .rubino/plugins/
297
+ - Rubino.plugin DSL
298
+
299
+ #### UI
300
+ - CLI adapter (TTY gems)
301
+ - Null adapter (testing)
302
+ - API adapter (structured events)
303
+ - Rich TUI with alternate screen buffer
304
+ - 4 themes (default, dark, light, monokai)
305
+ - Customizable keybinds
306
+ - Input history
307
+
308
+ #### Server
309
+ - JSON API server (WEBrick)
310
+ - REST endpoints for sessions, messages, tools, memory, jobs
311
+ - SSE event streaming (/events)
312
+ - Optional Basic Auth
313
+
314
+ #### Auth
315
+ - OAuth 2.1 client with PKCE
316
+ - Provider authentication (/connect flow)
317
+ - Token persistence (~/.rubino/oauth_tokens.json)
318
+ - GitHub, OpenAI, Anthropic, Google providers
319
+
320
+ #### Configuration
321
+ - YAML config with defaults
322
+ - Enhanced loader: multi-layer precedence
323
+ - Environment variable substitution ({env:VAR})
324
+ - File content inclusion ({file:path})
325
+ - Remote/managed config for enterprise
326
+ - RUBINO_* env var overrides
327
+
328
+ #### LSP
329
+ - JSON-RPC stdio client
330
+ - 37 language servers configured
331
+ - Auto-detection by file extension
332
+ - Operations: definition, references, hover, symbols, diagnostics
333
+ - LspTool for agent access
334
+
335
+ #### Other
336
+ - Session undo/redo via internal git snapshots
337
+ - Session forking at any message
338
+ - Session export (Markdown/JSON)
339
+ - Image support for vision models
340
+ - Custom user-defined tools (Ruby DSL)
341
+ - Code formatters (auto-format after edits)
342
+ - Network proxy support (HTTP/HTTPS/SOCKS)
343
+ - GitHub integration (gh CLI + REST API)
344
+ - Project context file discovery (.rubino.md, AGENTS.md, etc.)
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,69 @@
1
+ # Contributing to rubino
2
+
3
+ Thanks for helping improve rubino. This is the dev setup, test, and release flow.
4
+
5
+ ## Development setup
6
+
7
+ Requirements: Ruby 3.3.3 (see `.ruby-version` / `mise.toml`; the gem supports >= 3.1) and SQLite3.
8
+
9
+ ```bash
10
+ git clone https://github.com/Jhonnyr97/rubino-agent.git
11
+ cd rubino-agent
12
+ bundle install
13
+ bundle exec rubino setup # config + database in your home (or set RUBINO_HOME)
14
+ bundle exec rubino doctor # verify
15
+ ```
16
+
17
+ Run the CLI from the checkout with `bundle exec rubino <command>`.
18
+
19
+ > Tip: point `RUBINO_HOME` at a throwaway directory while developing so you don't touch your real `~/.rubino`. For LLM-free work, use the fake provider (`model.default: fake/happy-path` + `RUBINO_ALLOW_FAKE=1`).
20
+
21
+ ## Tests
22
+
23
+ ```bash
24
+ bundle exec rspec # full suite
25
+ bundle exec rspec path/to/file_spec.rb
26
+ bundle exec rake # default task == spec
27
+ ```
28
+
29
+ The HTTP boundary is locked by an end-to-end contract suite under `spec/rubino/api/contract/`. When the docs and the contract suite disagree, **the contract suite is canonical** — update the docs to match.
30
+
31
+ ## Lint
32
+
33
+ ```bash
34
+ bundle exec rubocop # rubocop + rubocop-rspec
35
+ bundle exec rubocop -A # autocorrect
36
+ ```
37
+
38
+ ## Pull requests
39
+
40
+ - Branch off `main`; keep changes focused.
41
+ - Add or update specs for behavior changes; touch tests only for what you change.
42
+ - Run `bundle exec rspec` and `bundle exec rubocop` before opening the PR.
43
+ - Update the relevant docs. The slash-command list (`docs/commands.md`) is sourced from `lib/rubino/commands/built_ins.rb` and the config reference (`docs/configuration.md`) from `lib/rubino/config/defaults.rb` — keep them in sync with code.
44
+ - Add a `CHANGELOG.md` entry (the project follows [keep-a-changelog](https://keepachangelog.com/)).
45
+
46
+ ## Anti-drift: single sources of truth
47
+
48
+ These enumerations live in code; docs must read from them, not duplicate them by hand:
49
+
50
+ - Slash commands ← `BuiltIns::DESCRIPTIONS` (also drives `/help` and tab-completion).
51
+ - Config keys & defaults ← `Config::Defaults::MODULE_DEFAULTS` (rich inline comments are the descriptions; `Defaults.to_yaml` dumps a commented YAML).
52
+ - Built-in tools ← `Tools::Registry#register_defaults!`.
53
+
54
+ ## Releasing
55
+
56
+ The gem version lives in `lib/rubino/version.rb`. Standard Bundler gem tasks are wired (`require "bundler/gem_tasks"`):
57
+
58
+ ```bash
59
+ # 1. bump lib/rubino/version.rb
60
+ # 2. update CHANGELOG.md (move Unreleased -> the new version)
61
+ # 3. build + tag + push the gem:
62
+ bundle exec rake release
63
+ ```
64
+
65
+ `rake release` builds the gem, creates the version git tag, pushes commits and the tag, and pushes the `.gem` to the configured host.
66
+
67
+ ## Architecture
68
+
69
+ See [docs/architecture.md](docs/architecture.md) and [AGENTS.md](AGENTS.md) for the layer diagram and the core loop overview.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jhon Rojas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.