rubino-agent 0.4.0 → 0.5.1

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 (322) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/.rubocop_todo.yml +12 -2
  4. data/AGENTS.md +1 -1
  5. data/CHANGELOG.md +454 -1
  6. data/CONTRIBUTING.md +10 -1
  7. data/README.md +69 -11
  8. data/Rakefile +48 -0
  9. data/docs/agents.md +82 -48
  10. data/docs/architecture.md +4 -11
  11. data/docs/commands.md +46 -7
  12. data/docs/configuration.md +174 -30
  13. data/docs/getting-started.md +5 -3
  14. data/docs/mcp.md +3 -3
  15. data/docs/memory.md +3 -3
  16. data/docs/security.md +17 -6
  17. data/docs/tools.md +45 -49
  18. data/docs/troubleshooting.md +1 -1
  19. data/exe/rubino +16 -2
  20. data/ext/landlock/extconf.rb +78 -0
  21. data/ext/landlock/landlock.c +253 -0
  22. data/install.sh +715 -54
  23. data/lib/rubino/active_agent.rb +73 -0
  24. data/lib/rubino/agent/action_claim_guard.rb +913 -0
  25. data/lib/rubino/agent/agent_registry.rb +5 -2
  26. data/lib/rubino/agent/definition.rb +4 -28
  27. data/lib/rubino/agent/fallback_chain.rb +0 -6
  28. data/lib/rubino/agent/iteration_budget.rb +109 -3
  29. data/lib/rubino/agent/loop.rb +664 -42
  30. data/lib/rubino/agent/model_call_runner.rb +81 -3
  31. data/lib/rubino/agent/prompts/build.txt +55 -7
  32. data/lib/rubino/agent/prompts/general.txt +8 -3
  33. data/lib/rubino/agent/response_validator.rb +8 -0
  34. data/lib/rubino/agent/runner.rb +307 -13
  35. data/lib/rubino/agent/tool_executor.rb +368 -31
  36. data/lib/rubino/agent/truncation_continuation.rb +11 -5
  37. data/lib/rubino/api/operations/approvals/decide_operation.rb +0 -4
  38. data/lib/rubino/api/operations/clarifications/decide_operation.rb +0 -4
  39. data/lib/rubino/api/operations/cron_jobs/create_operation.rb +0 -4
  40. data/lib/rubino/api/operations/cron_jobs/delete_operation.rb +0 -4
  41. data/lib/rubino/api/operations/cron_jobs/list_operation.rb +0 -4
  42. data/lib/rubino/api/operations/cron_jobs/pause_operation.rb +1 -5
  43. data/lib/rubino/api/operations/cron_jobs/resume_operation.rb +1 -5
  44. data/lib/rubino/api/operations/cron_jobs/show_operation.rb +0 -4
  45. data/lib/rubino/api/operations/cron_jobs/trigger_operation.rb +0 -4
  46. data/lib/rubino/api/operations/cron_jobs/update_operation.rb +0 -4
  47. data/lib/rubino/api/operations/files/read_operation.rb +1 -5
  48. data/lib/rubino/api/operations/files/upload_operation.rb +0 -4
  49. data/lib/rubino/api/operations/health_operation.rb +1 -5
  50. data/lib/rubino/api/operations/memory/delete_operation.rb +0 -4
  51. data/lib/rubino/api/operations/memory/index_operation.rb +0 -4
  52. data/lib/rubino/api/operations/memory/stats_operation.rb +0 -4
  53. data/lib/rubino/api/operations/metrics_operation.rb +1 -1
  54. data/lib/rubino/api/operations/mode/show_operation.rb +0 -4
  55. data/lib/rubino/api/operations/mode/update_operation.rb +0 -4
  56. data/lib/rubino/api/operations/models/list_operation.rb +0 -4
  57. data/lib/rubino/api/operations/oauth/connections/disconnect_operation.rb +0 -4
  58. data/lib/rubino/api/operations/oauth/connections/list_operation.rb +0 -4
  59. data/lib/rubino/api/operations/oauth/providers/callback_operation.rb +0 -4
  60. data/lib/rubino/api/operations/oauth/providers/connect_operation.rb +0 -4
  61. data/lib/rubino/api/operations/oauth/providers/list_operation.rb +0 -4
  62. data/lib/rubino/api/operations/runs/create_operation.rb +0 -4
  63. data/lib/rubino/api/operations/runs/events_operation.rb +0 -4
  64. data/lib/rubino/api/operations/runs/stop_operation.rb +0 -4
  65. data/lib/rubino/api/operations/sessions/create_operation.rb +0 -4
  66. data/lib/rubino/api/operations/sessions/delete_operation.rb +0 -4
  67. data/lib/rubino/api/operations/sessions/index_operation.rb +0 -4
  68. data/lib/rubino/api/operations/sessions/retry_operation.rb +0 -4
  69. data/lib/rubino/api/operations/sessions/show_operation.rb +0 -4
  70. data/lib/rubino/api/operations/sessions/undo_operation.rb +0 -4
  71. data/lib/rubino/api/operations/skills/list_operation.rb +0 -4
  72. data/lib/rubino/api/operations/skills/toggle_operation.rb +0 -4
  73. data/lib/rubino/api/operations/tasks/index_operation.rb +0 -4
  74. data/lib/rubino/api/operations/tasks/show_operation.rb +0 -4
  75. data/lib/rubino/api/operations/tasks/stop_operation.rb +0 -4
  76. data/lib/rubino/api/router.rb +2 -2
  77. data/lib/rubino/api/server.rb +19 -0
  78. data/lib/rubino/attachments/policy.rb +8 -0
  79. data/lib/rubino/attachments/preamble.rb +16 -8
  80. data/lib/rubino/boot/config_guard.rb +71 -0
  81. data/lib/rubino/cli/chat/completion_builder.rb +44 -8
  82. data/lib/rubino/cli/chat/idle_card_host.rb +7 -1
  83. data/lib/rubino/cli/chat/session_resolver.rb +186 -50
  84. data/lib/rubino/cli/chat_command.rb +1724 -91
  85. data/lib/rubino/cli/commands.rb +373 -1
  86. data/lib/rubino/cli/config_command.rb +118 -11
  87. data/lib/rubino/cli/doctor_command.rb +268 -23
  88. data/lib/rubino/cli/jobs_command.rb +42 -3
  89. data/lib/rubino/cli/memory_command.rb +76 -23
  90. data/lib/rubino/cli/onboarding_wizard.rb +85 -7
  91. data/lib/rubino/cli/server_command.rb +43 -1
  92. data/lib/rubino/cli/session_command.rb +272 -18
  93. data/lib/rubino/cli/setup_command.rb +293 -8
  94. data/lib/rubino/cli/skills_command.rb +88 -20
  95. data/lib/rubino/cli/trust_gate.rb +16 -7
  96. data/lib/rubino/commands/built_ins.rb +4 -2
  97. data/lib/rubino/commands/command.rb +12 -2
  98. data/lib/rubino/commands/executor.rb +161 -19
  99. data/lib/rubino/commands/handlers/agent_switch.rb +100 -0
  100. data/lib/rubino/commands/handlers/agents.rb +324 -60
  101. data/lib/rubino/commands/handlers/config.rb +8 -1
  102. data/lib/rubino/commands/handlers/display.rb +50 -0
  103. data/lib/rubino/commands/handlers/help.rb +106 -14
  104. data/lib/rubino/commands/handlers/mcp.rb +7 -32
  105. data/lib/rubino/commands/handlers/memory.rb +23 -38
  106. data/lib/rubino/commands/handlers/sessions.rb +70 -33
  107. data/lib/rubino/commands/handlers/skills.rb +47 -28
  108. data/lib/rubino/commands/handlers/status.rb +65 -10
  109. data/lib/rubino/commands/loader.rb +12 -0
  110. data/lib/rubino/compression/compression_result.rb +35 -0
  111. data/lib/rubino/compression/compressor.rb +109 -0
  112. data/lib/rubino/compression/content_router.rb +240 -0
  113. data/lib/rubino/compression/diff_compressor.rb +252 -0
  114. data/lib/rubino/compression/javascript_code_skeleton.rb +15 -0
  115. data/lib/rubino/compression/json_compressor.rb +274 -0
  116. data/lib/rubino/compression/line_skeleton.rb +92 -0
  117. data/lib/rubino/compression/log_compressor.rb +299 -0
  118. data/lib/rubino/compression/python_code_skeleton.rb +122 -0
  119. data/lib/rubino/compression/ruby_code_skeleton.rb +80 -0
  120. data/lib/rubino/compression/tree_sitter_code_skeleton.rb +118 -0
  121. data/lib/rubino/compression/tsx_code_skeleton.rb +15 -0
  122. data/lib/rubino/compression/typescript_code_skeleton.rb +15 -0
  123. data/lib/rubino/config/configuration.rb +151 -105
  124. data/lib/rubino/config/defaults.rb +369 -41
  125. data/lib/rubino/config/loader.rb +71 -13
  126. data/lib/rubino/config/reasoning_prefs.rb +23 -0
  127. data/lib/rubino/config/validator.rb +384 -0
  128. data/lib/rubino/config/writer.rb +123 -31
  129. data/lib/rubino/context/compressor.rb +185 -23
  130. data/lib/rubino/context/file_discovery.rb +0 -8
  131. data/lib/rubino/context/message_boundary.rb +26 -5
  132. data/lib/rubino/context/project_languages.rb +83 -0
  133. data/lib/rubino/context/prompt_assembler.rb +110 -22
  134. data/lib/rubino/context/summary_builder.rb +77 -27
  135. data/lib/rubino/context/token_budget.rb +38 -13
  136. data/lib/rubino/context/token_estimate.rb +45 -0
  137. data/lib/rubino/context/tool_result_pruner.rb +81 -0
  138. data/lib/rubino/database/connection.rb +154 -3
  139. data/lib/rubino/database/migrations/001_create_initial_schema.rb +314 -40
  140. data/lib/rubino/database/migrator.rb +81 -14
  141. data/lib/rubino/documents/cap_exceeded.rb +13 -0
  142. data/lib/rubino/documents/converters/csv.rb +4 -3
  143. data/lib/rubino/documents/converters/docx.rb +29 -5
  144. data/lib/rubino/documents/converters/html.rb +5 -1
  145. data/lib/rubino/documents/converters/json.rb +2 -1
  146. data/lib/rubino/documents/converters/pdf.rb +11 -2
  147. data/lib/rubino/documents/converters/plain.rb +2 -1
  148. data/lib/rubino/documents/converters/pptx.rb +11 -2
  149. data/lib/rubino/documents/converters/xlsx.rb +35 -4
  150. data/lib/rubino/documents/converters/xml.rb +2 -1
  151. data/lib/rubino/documents/limits.rb +210 -0
  152. data/lib/rubino/documents.rb +10 -3
  153. data/lib/rubino/errors.rb +36 -5
  154. data/lib/rubino/files/workspace.rb +2 -2
  155. data/lib/rubino/interaction/cancel_token.rb +19 -3
  156. data/lib/rubino/interaction/events.rb +13 -3
  157. data/lib/rubino/interaction/input_queue.rb +11 -0
  158. data/lib/rubino/interaction/lifecycle.rb +238 -33
  159. data/lib/rubino/interaction/polishing.rb +184 -0
  160. data/lib/rubino/interaction/probe.rb +1 -1
  161. data/lib/rubino/jobs/cron_job_repository.rb +5 -12
  162. data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +11 -0
  163. data/lib/rubino/jobs/handlers/distill_skill_job.rb +67 -21
  164. data/lib/rubino/jobs/queue.rb +133 -13
  165. data/lib/rubino/jobs/runner.rb +24 -6
  166. data/lib/rubino/jobs/worker.rb +1 -5
  167. data/lib/rubino/llm/adapter_factory.rb +1 -1
  168. data/lib/rubino/llm/adapter_response.rb +47 -4
  169. data/lib/rubino/llm/auxiliary_client.rb +63 -3
  170. data/lib/rubino/llm/cache_breakpoint_middleware.rb +194 -0
  171. data/lib/rubino/llm/credential_check.rb +76 -20
  172. data/lib/rubino/llm/error_classifier.rb +186 -77
  173. data/lib/rubino/llm/fake_provider.rb +3 -3
  174. data/lib/rubino/llm/inline_think_filter.rb +103 -15
  175. data/lib/rubino/llm/reasoning_manager.rb +3 -26
  176. data/lib/rubino/llm/request.rb +26 -15
  177. data/lib/rubino/llm/ruby_llm_adapter.rb +623 -67
  178. data/lib/rubino/llm/scenario_loader.rb +10 -17
  179. data/lib/rubino/llm/scenarios/glued-table-prose.yml +36 -0
  180. data/lib/rubino/llm/scenarios/growing-table.yml +49 -0
  181. data/lib/rubino/llm/scenarios/narrow-terminal-table.yml +47 -0
  182. data/lib/rubino/llm/scenarios/streamed-table.yml +55 -0
  183. data/lib/rubino/llm/scenarios/table-then-prose.yml +34 -0
  184. data/lib/rubino/llm/scenarios/too-wide-table.yml +47 -0
  185. data/lib/rubino/llm/scenarios/wide-table.yml +1 -1
  186. data/lib/rubino/llm/thinking_support.rb +17 -12
  187. data/lib/rubino/llm/tool_bridge.rb +200 -32
  188. data/lib/rubino/mcp/manager.rb +71 -10
  189. data/lib/rubino/mcp/mcp_tool_wrapper.rb +38 -3
  190. data/lib/rubino/memory/aux_retry.rb +107 -0
  191. data/lib/rubino/memory/backends/sqlite.rb +104 -67
  192. data/lib/rubino/memory/backends.rb +26 -10
  193. data/lib/rubino/memory/deduplicator.rb +22 -0
  194. data/lib/rubino/memory/flusher.rb +35 -1
  195. data/lib/rubino/memory/salience_gate.rb +129 -0
  196. data/lib/rubino/memory/sqlite_extraction.rb +70 -0
  197. data/lib/rubino/memory/sqlite_extraction_prompt.rb +16 -1
  198. data/lib/rubino/memory/store.rb +48 -20
  199. data/lib/rubino/memory/threat_scanner.rb +60 -0
  200. data/lib/rubino/memory.rb +47 -0
  201. data/lib/rubino/oauth/provider.rb +0 -5
  202. data/lib/rubino/output/cost.rb +52 -0
  203. data/lib/rubino/output/headless_block_latch.rb +53 -0
  204. data/lib/rubino/output/result_serializer.rb +222 -0
  205. data/lib/rubino/output/turn_recorder.rb +77 -0
  206. data/lib/rubino/run/event_store.rb +1 -6
  207. data/lib/rubino/run/repository.rb +0 -14
  208. data/lib/rubino/security/approval_policy.rb +314 -33
  209. data/lib/rubino/security/command_allowlist.rb +79 -4
  210. data/lib/rubino/security/command_normalizer.rb +36 -0
  211. data/lib/rubino/security/dangerous_patterns.rb +17 -4
  212. data/lib/rubino/security/doom_loop_detector.rb +21 -2
  213. data/lib/rubino/security/hardline_guard.rb +190 -16
  214. data/lib/rubino/security/pattern_matcher.rb +28 -5
  215. data/lib/rubino/security/prefix_deriver.rb +25 -6
  216. data/lib/rubino/security/readonly_commands.rb +442 -18
  217. data/lib/rubino/security/redactor.rb +272 -0
  218. data/lib/rubino/security/sandbox.rb +460 -0
  219. data/lib/rubino/security/secret_detector.rb +110 -0
  220. data/lib/rubino/security/secret_path.rb +263 -0
  221. data/lib/rubino/security/url_safety.rb +255 -0
  222. data/lib/rubino/session/lock.rb +91 -0
  223. data/lib/rubino/session/message.rb +38 -3
  224. data/lib/rubino/session/picker.rb +95 -0
  225. data/lib/rubino/session/repository.rb +249 -31
  226. data/lib/rubino/session/store.rb +135 -21
  227. data/lib/rubino/skills/installer.rb +116 -32
  228. data/lib/rubino/skills/prompt_index.rb +2 -2
  229. data/lib/rubino/skills/registry.rb +56 -6
  230. data/lib/rubino/skills/skill.rb +94 -12
  231. data/lib/rubino/skills/skill_tool.rb +21 -25
  232. data/lib/rubino/skills/state_repository.rb +0 -4
  233. data/lib/rubino/tools/background_tasks.rb +299 -47
  234. data/lib/rubino/tools/base.rb +219 -4
  235. data/lib/rubino/tools/edit_tool.rb +116 -31
  236. data/lib/rubino/tools/fuzzy_match.rb +212 -0
  237. data/lib/rubino/tools/glob_tool.rb +52 -9
  238. data/lib/rubino/tools/grep_tool.rb +71 -11
  239. data/lib/rubino/tools/multi_edit_tool.rb +88 -20
  240. data/lib/rubino/tools/patch_tool.rb +56 -10
  241. data/lib/rubino/tools/probe_tool.rb +0 -20
  242. data/lib/rubino/tools/question_tool.rb +54 -2
  243. data/lib/rubino/tools/read_attachment_tool.rb +24 -12
  244. data/lib/rubino/tools/read_tool.rb +159 -35
  245. data/lib/rubino/tools/read_tracker.rb +189 -35
  246. data/lib/rubino/tools/registry.rb +151 -31
  247. data/lib/rubino/tools/result.rb +48 -9
  248. data/lib/rubino/tools/retrieve_output_tool.rb +70 -0
  249. data/lib/rubino/tools/ruby_tool.rb +0 -0
  250. data/lib/rubino/tools/shell_kill_tool.rb +6 -2
  251. data/lib/rubino/tools/shell_output_tool.rb +7 -1
  252. data/lib/rubino/tools/shell_registry.rb +229 -5
  253. data/lib/rubino/tools/shell_tail_tool.rb +6 -1
  254. data/lib/rubino/tools/shell_tool.rb +523 -54
  255. data/lib/rubino/tools/steer_tool.rb +2 -21
  256. data/lib/rubino/tools/subagent_probe.rb +1 -1
  257. data/lib/rubino/tools/summarize_file_tool.rb +12 -0
  258. data/lib/rubino/tools/task_result_tool.rb +8 -2
  259. data/lib/rubino/tools/task_stop_tool.rb +15 -22
  260. data/lib/rubino/tools/task_tool.rb +229 -104
  261. data/lib/rubino/tools/vision_tool.rb +37 -4
  262. data/lib/rubino/tools/webfetch_tool.rb +184 -7
  263. data/lib/rubino/tools/websearch_tool.rb +92 -30
  264. data/lib/rubino/tools/write_tool.rb +24 -5
  265. data/lib/rubino/ui/agent_menu.rb +179 -0
  266. data/lib/rubino/ui/api.rb +12 -3
  267. data/lib/rubino/ui/base.rb +13 -2
  268. data/lib/rubino/ui/bottom_composer.rb +1483 -203
  269. data/lib/rubino/ui/cli.rb +1340 -272
  270. data/lib/rubino/ui/completion_menu.rb +35 -50
  271. data/lib/rubino/ui/composer/input_line.rb +131 -0
  272. data/lib/rubino/ui/composer/subagent_panel.rb +35 -0
  273. data/lib/rubino/ui/headless_trace.rb +63 -0
  274. data/lib/rubino/ui/input_history.rb +90 -5
  275. data/lib/rubino/ui/live_region.rb +82 -7
  276. data/lib/rubino/ui/markdown_renderer.rb +214 -17
  277. data/lib/rubino/ui/menu_view.rb +117 -0
  278. data/lib/rubino/ui/notifier.rb +0 -2
  279. data/lib/rubino/ui/null.rb +53 -6
  280. data/lib/rubino/ui/paste_store.rb +49 -3
  281. data/lib/rubino/ui/printer_base.rb +135 -8
  282. data/lib/rubino/ui/queued_indicators.rb +6 -1
  283. data/lib/rubino/ui/status_bar.rb +61 -7
  284. data/lib/rubino/ui/streaming_markdown.rb +148 -6
  285. data/lib/rubino/ui/subagent_cards.rb +126 -25
  286. data/lib/rubino/ui/tool_label.rb +52 -0
  287. data/lib/rubino/update_check.rb +39 -4
  288. data/lib/rubino/util/atomic_file.rb +129 -0
  289. data/lib/rubino/util/duration.rb +8 -5
  290. data/lib/rubino/util/ignore_rules.rb +120 -0
  291. data/lib/rubino/util/output.rb +275 -13
  292. data/lib/rubino/util/secrets_mask.rb +70 -7
  293. data/lib/rubino/util/spill_store.rb +153 -0
  294. data/lib/rubino/version.rb +7 -1
  295. data/lib/rubino/workspace.rb +74 -3
  296. data/lib/rubino.rb +216 -25
  297. data/rubino-agent.gemspec +28 -1
  298. data/skills/ruby-expert/SKILL.md +1 -0
  299. metadata +116 -29
  300. data/docs/plugins.md +0 -195
  301. data/lib/rubino/agent/router.rb +0 -65
  302. data/lib/rubino/database/migrations/002_create_runs.rb +0 -45
  303. data/lib/rubino/database/migrations/003_create_skill_states.rb +0 -15
  304. data/lib/rubino/database/migrations/004_create_cron_jobs.rb +0 -36
  305. data/lib/rubino/database/migrations/005_create_oauth_connections.rb +0 -27
  306. data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +0 -34
  307. data/lib/rubino/database/migrations/007_create_messages_fts.rb +0 -59
  308. data/lib/rubino/database/migrations/008_create_memory_facts.rb +0 -75
  309. data/lib/rubino/database/migrations/009_create_memory_graph.rb +0 -55
  310. data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +0 -20
  311. data/lib/rubino/interaction/state.rb +0 -56
  312. data/lib/rubino/memory/backends/default.rb +0 -101
  313. data/lib/rubino/memory/extractor.rb +0 -85
  314. data/lib/rubino/memory/retriever.rb +0 -50
  315. data/lib/rubino/plugins/registry.rb +0 -75
  316. data/lib/rubino/plugins.rb +0 -86
  317. data/lib/rubino/tools/answer_child_tool.rb +0 -83
  318. data/lib/rubino/tools/ask_parent_tool.rb +0 -232
  319. data/lib/rubino/tools/git_tool.rb +0 -71
  320. data/lib/rubino/tools/github_tool.rb +0 -233
  321. data/lib/rubino/tools/test_tool.rb +0 -454
  322. data/lib/rubino/ui/subagent_view.rb +0 -266
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a20b85c30f537ff5bdaf5abec6300eedfcda791d76b899ac3fcd972b71854ac1
4
- data.tar.gz: d5692a31f13e338690a3be90bc4d96d27251ddad165b706afee7e0fcdee3fea7
3
+ metadata.gz: c1debe685b923c625e0dc4dcf95da3c9fc12fcd6c73bdff71e35164279e62b06
4
+ data.tar.gz: 5451e122fc13bfdd4ffeba0e680cad9fb6b976dfabe8f9dcfd894e5215ac9688
5
5
  SHA512:
6
- metadata.gz: 865a203c91311f039d90a98b4e88ff8f792d0049e7370f10c847e2aeb77458f2f39d80485f133a79d8c2e8d9c1eeb5dec369f53320d210ff79928965a8c0a3f2
7
- data.tar.gz: a9435d6746bc743e65db73574e13d4cd65445bf97ec5c9e2387737c176e7ab8726991062cc852a6c295910e3876d8b4a1d8c96420f5a8dd6a83dabf68a280546
6
+ metadata.gz: bf657914d128053ffa39d7911a5c2c12e491ff45b1907e8bc78a694b8f8d7540a1e8d1182ee5831670af17e5e64b16148202d987ebc9e2c75604a88f78148d36
7
+ data.tar.gz: eefe6fbbcd977bff1cf8b7a189fdaf73daee9ca6b12ca55b82876a99c55ede529dc78ba3f5146f8b34a7e6e6b6b38dab454c9d6cdcf3baca8e754187f49c6829
data/.rubocop.yml CHANGED
@@ -27,6 +27,12 @@ AllCops:
27
27
  # Test fixtures are sample input documents (e.g. a .rb code sample for the
28
28
  # plain-text converter), not project source -- they must not be linted.
29
29
  - "spec/fixtures/**/*"
30
+ # Eval-harness fixtures are deliberately tiny/imperfect sample projects the
31
+ # agent edits at eval time (INPUT, not source); results/ is generated output.
32
+ # The eval/.rubocop.yml excludes these for an in-eval run; mirror it here so
33
+ # the whole-repo lint from the root is clean too.
34
+ - "eval/fixtures/**/*"
35
+ - "eval/results/**/*"
30
36
 
31
37
  # --- House style: strings ----------------------------------------------------
32
38
 
data/.rubocop_todo.yml CHANGED
@@ -256,6 +256,8 @@ Metrics/BlockLength:
256
256
  Metrics/ClassLength:
257
257
  Exclude:
258
258
  - 'lib/rubino/cli/chat_command.rb'
259
+ - 'lib/rubino/commands/executor.rb'
260
+ - 'lib/rubino/llm/ruby_llm_adapter.rb'
259
261
  - 'lib/rubino/ui/bottom_composer.rb'
260
262
  - 'lib/rubino/ui/cli.rb'
261
263
 
@@ -406,7 +408,6 @@ Naming/PredicateMethod:
406
408
  Exclude:
407
409
  - 'lib/rubino/agent/model_call_runner.rb'
408
410
  - 'lib/rubino/agent/response_validator.rb'
409
- - 'lib/rubino/agent/router.rb'
410
411
  - 'lib/rubino/cli/onboarding_wizard.rb'
411
412
  - 'lib/rubino/cli/trust_gate.rb'
412
413
  - 'lib/rubino/memory/backends/sqlite.rb'
@@ -451,6 +452,7 @@ RSpec/AnyInstance:
451
452
  - 'spec/rubino/commands/commands_spec.rb'
452
453
  - 'spec/rubino/context/compressor_spec.rb'
453
454
  - 'spec/rubino/context/prompt_assembler_active_skill_spec.rb'
455
+ - 'spec/rubino/context/prompt_assembler_cache_breakpoints_spec.rb'
454
456
  - 'spec/rubino/context/prompt_assembler_ignore_rules_spec.rb'
455
457
  - 'spec/rubino/context/prompt_assembler_layering_spec.rb'
456
458
  - 'spec/rubino/context/prompt_assembler_memory_snapshot_spec.rb'
@@ -462,8 +464,10 @@ RSpec/BeforeAfterAll:
462
464
  - '**/spec/spec_helper.rb'
463
465
  - '**/spec/rails_helper.rb'
464
466
  - '**/spec/support/**/*.rb'
467
+ - 'spec/rubino/llm/tool_bridge_cache_breakpoint_spec.rb'
465
468
  - 'spec/rubino/security/approval_policy_mode_spec.rb'
466
469
  - 'spec/rubino/tools/registry_mode_spec.rb'
470
+ - 'spec/rubino/tools/registry_situational_gating_spec.rb'
467
471
  - 'spec/rubino/tools/registry_spec.rb'
468
472
 
469
473
  # Offense count: 31
@@ -534,6 +538,7 @@ RSpec/DescribeClass:
534
538
  - 'spec/rubino/skills/skills_spec.rb'
535
539
  - 'spec/rubino/tools/edit_read_gate_spec.rb'
536
540
  - 'spec/rubino/tools/shell_background_spec.rb'
541
+ - 'spec/rubino/tools/shell_background_completion_spec.rb'
537
542
  - 'spec/rubino/tools/shell_input_spec.rb'
538
543
  - 'spec/rubino/tools/tool_fixes_spec.rb'
539
544
  - 'spec/rubino/ui/bottom_composer_approval_handoff_pty_spec.rb'
@@ -550,7 +555,11 @@ RSpec/DescribeMethod:
550
555
  - 'spec/rubino/api/middleware/json_body_size_spec.rb'
551
556
  - 'spec/rubino/cli/chat_command_continue_spec.rb'
552
557
  - 'spec/rubino/cli/commands_version_spec.rb'
558
+ - 'spec/rubino/llm/ruby_llm_adapter_cache_spec.rb'
559
+ - 'spec/rubino/llm/tool_bridge_cache_breakpoint_spec.rb'
560
+ - 'spec/rubino/tools/registry_situational_gating_spec.rb'
553
561
  - 'spec/rubino/context/prompt_assembler_active_skill_spec.rb'
562
+ - 'spec/rubino/context/prompt_assembler_cache_breakpoints_spec.rb'
554
563
  - 'spec/rubino/context/prompt_assembler_ignore_rules_spec.rb'
555
564
  - 'spec/rubino/context/prompt_assembler_layering_spec.rb'
556
565
  - 'spec/rubino/context/prompt_assembler_memory_snapshot_spec.rb'
@@ -654,12 +663,13 @@ RSpec/LeakyLocalVariable:
654
663
  Exclude:
655
664
  - 'spec/rubino/no_direct_output_spec.rb'
656
665
 
657
- # Offense count: 7
666
+ # Offense count: 8
658
667
  RSpec/MultipleDescribes:
659
668
  Exclude:
660
669
  - 'spec/rubino/commands/commands_spec.rb'
661
670
  - 'spec/rubino/context/prompt_assembler_layering_spec.rb'
662
671
  - 'spec/rubino/memory/backends_spec.rb'
672
+ - 'spec/rubino/tools/encoding_robustness_spec.rb'
663
673
  - 'spec/rubino/tools/error_code_spec.rb'
664
674
  - 'spec/rubino/tools/tool_fixes_spec.rb'
665
675
  - 'spec/rubino/trust_spec.rb'
data/AGENTS.md CHANGED
@@ -77,7 +77,7 @@ lib/rubino/
77
77
  ## Surfaces this project exposes
78
78
 
79
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}`.
80
+ - **CLI** — `rubino {setup,chat,prompt,server,config,memory,sessions,jobs,skills,tools,tls_cert,doctor,version,update}`.
81
81
  - **Library** — `require "rubino"; Rubino.run(...)`.
82
82
 
83
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.
data/CHANGELOG.md CHANGED
@@ -1,6 +1,459 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.5.1] - 2026-06-25
4
+
5
+ ### Added
6
+
7
+ - **Tool-output compression (deterministic, off by default).** A no-LLM content
8
+ router at the single `Agent::ToolExecutor` seam compresses high-volume tool
9
+ output before it reaches the model: test/build/lint logs are reduced to their
10
+ failures + summary (≈97% fewer tokens on a failing suite, every failure kept),
11
+ and a whole-file source read can be returned as a skeleton (signatures kept,
12
+ large bodies elided behind a `read offset:/limit:` pointer). Diffs, grep/search
13
+ results, JSON, and short output pass through **byte-identical**. Reversibility
14
+ reuses the existing spill: the full original is written to
15
+ `tool-results/<call_id>.txt` and the compressed output points the model there —
16
+ no separate store/tool. When enabled, `read` and `shell` expose a `compress`
17
+ parameter (default true) so the model can opt a single call out and get the
18
+ verbatim output. Master switch `tool_output_compression.enabled` (default
19
+ `false`); `rubino setup` offers to turn it on. See
20
+ [configuration.md](docs/configuration.md#tool_output_compression).
21
+ - **Multi-language code compression.** The whole-file source-skeleton compressor
22
+ now covers more than Ruby. `tool_output_compression.code.languages` (default
23
+ `["ruby"]`) selects which languages get skeletonised: Ruby (built-in Prism
24
+ parser), Python (stdlib `ast` via your `python3` — a no-op if `python3` isn't
25
+ on PATH), and JavaScript / TypeScript / TSX (via the optional
26
+ `tree_sitter_language_pack` gem — a no-op until it's installed). A read in an
27
+ unlisted language passes through verbatim. `rubino setup` adds a language
28
+ picker and, if you choose JS/TS, offers to install the parser gem.
29
+ - **Agent-attach view.** At the idle prompt, `↓` opens the subagent picker and
30
+ `Enter` now **attaches** to the highlighted background subagent: the screen
31
+ switches to that agent's OWN full timeline (its tool calls and what it said,
32
+ replayed from its session) and the input prompt becomes scoped — `sa_xxxx ❯`.
33
+ While attached, typed text steers the running child (or answers it when it's
34
+ blocked on you); `←` on the empty prompt (or the picker's `◂ main` row) returns
35
+ to the main timeline, and the picker doubles as a switcher between agents. This
36
+ replaces the bounded registry snapshot the picker's Enter used to show with the
37
+ agent's real conversation, and makes the global `/agents <id> steer/probe` and
38
+ `/reply <id>` forms redundant while attached. The attached view **live-tails**
39
+ the child's stream (tool rows and streaming prose) exactly like the main agent
40
+ instead of freezing on a snapshot, and `/back` / `/detach` return to the main
41
+ agent regardless of composer-draft state (#82, #85, #87).
42
+ - **`api.allow_public_bind` gate.** Because the API server can execute shell
43
+ tools, binding it to a non-loopback address (`--host 0.0.0.0`,
44
+ `RUBINO_API_HOST`) now **refuses to boot** unless `api.allow_public_bind: true`
45
+ is set in `config.yml`; when opted in, the server prints a one-time exposure
46
+ warning. Loopback binds are unaffected (#577).
47
+ - **MCP tool transparency + parallel startup.** An MCP tool's display label now
48
+ carries its source — the live tool card and the approval card both show
49
+ `<bare> (mcp:<server>)`, so you can tell at a glance that an out-of-process
50
+ server is running (the model-facing tool name is unchanged) (#582). MCP
51
+ servers also now connect **in parallel** at boot, so one hanging server no
52
+ longer serializes startup (#576).
53
+ - **Read-only meta-commands run immediately while a turn is active.** A small
54
+ set of non-mutating slash commands (`/agents`, `/tasks`, `/stop`, `/status`,
55
+ `/jobs`, `/help`, `/commands`, `/dirs`) now execute **immediately** mid-turn
56
+ instead of queuing — so you can drill into a sub-agent, stop the run, or check
57
+ status without interrupting. State-mutating commands (`/model`, `/clear`,
58
+ `/new`, `/config`, `/mode`, …) show a transient `⚠ <cmd> is not available
59
+ during an active turn — press Esc to interrupt first` notice; plain text still
60
+ queues, and `Esc` interrupts.
61
+ - **Interactive CLI session picker.** A bare `rubino sessions` on a TTY opens an
62
+ interactive picker (id, title, message count, dir, age; arrow-key highlight,
63
+ type-to-filter, `Esc` cancels) and `Enter` resumes the chosen session. On a
64
+ pipe / non-TTY it prints a script-safe list; `sessions list` stays list-only.
65
+ The picker is cwd-scoped by default; `--all` unscopes it.
66
+ - **`/sessions rename <id|title> <new title>`.** Rename a session from the REPL
67
+ (#45).
68
+ - **Aux-LLM session titles.** When `auxiliary.title` names a concrete backend,
69
+ new sessions get an LLM-generated, length-capped summary title; the
70
+ deterministic derivation stays the default and the fallback (#45).
71
+ - **Streaming GFM table rendering (#89).** A markdown table now renders as a
72
+ live, correctly-fitted table as it streams — a sliding window of recent rows
73
+ grows in place — instead of leaking raw `| col | col |` pipes that only snap
74
+ into a table once the message completes.
75
+
76
+ ### Changed
77
+
78
+ - **Provider auto-routing.** With `model.provider: "auto"` (the default), the
79
+ concrete provider is derived from the model id (`openai/*` → OpenAI); the
80
+ setup wizard / auto-detect write an explicit provider when a non-OpenAI
81
+ backend is chosen.
82
+ - **Credential check uses provider-specific env vars.** The credential check
83
+ and key resolution now read the env var for the configured provider
84
+ (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `BEDROCK_API_KEY`,
85
+ `MINIMAX_API_KEY`, and `<PROVIDER>_API_KEY` for anything else, e.g.
86
+ `DEEPSEEK_API_KEY`). A non-OpenAI provider no longer silently falls back to
87
+ `OPENAI_API_KEY` (only providers explicitly marked `openai_compatible` /
88
+ `anthropic_compatible` fall back to `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`).
89
+ - **`security.confirm_policy` default is `dangerous_only`.** Safe shell commands
90
+ run unprompted; only commands matching a dangerous pattern prompt. Set
91
+ `confirm_policy: confirm_all` to restore prompt-on-everything. The
92
+ non-bypassable hardline floor and `permissions: deny` always run first
93
+ regardless of policy.
94
+ - **Removed the built-in `run_tests` and `github` tools.** Running tests and
95
+ GitHub/git operations now go through the generic `shell` tool (with its
96
+ hardened git arg parsing), matching the field norm and shrinking the tool
97
+ surface.
98
+ - **Blocked-tool results are now typed errors.** When a tool call is blocked
99
+ (denied by approval, sandbox, or policy), its result is returned to the model
100
+ as a typed error with explicit anti-confabulation wording, so the model is told
101
+ the action did NOT happen instead of being free to assume success (#583).
102
+ - **Single status bar during a turn.** The animated facet activity row is folded
103
+ into the model/ctx footer (one bar, not two); the "esc to interrupt" hint shows
104
+ exactly once, and a mid-stream **waiting indicator** resurfaces beneath the
105
+ in-flight tail after a short window of model/transport silence and drops away
106
+ the instant tokens resume (#21, #56b — `/status` now also shows the workspace
107
+ cwd line).
108
+ - **FIFO approval queue for concurrent subagents.** When multiple subagents need
109
+ approval at once, one modal shows at a time with an "(N more queued)"
110
+ indicator that dequeues on resolve, and async-completion notices no longer
111
+ print over an active modal. Subagent approvals also **escalate to the parent's
112
+ approval card at any nesting depth**, so a nested child no longer fail-closes
113
+ with a noninteractive block (#86).
114
+ - **Slash commands dispatch while attached to a subagent** (`/stop <id>`,
115
+ `/agents`, `/status`, …) instead of being steered into the child as text;
116
+ `/skills list` / `/skills ls` show the skills list rather than trying to
117
+ activate a skill named `list`; `/think off` hides the reasoning aside for
118
+ always-thinking models unless an explicit `/reasoning` is set; `/config <key>`
119
+ resolves the short labels `/status` advertises (`reasoning`, `effort`,
120
+ `think`) (#62, #66, #87).
121
+ - **`/new` returns instantly.** The end-of-session memory flush is enqueued as a
122
+ background job instead of running a synchronous aux-LLM extract, so starting a
123
+ new session no longer freezes the prompt for 2–3s.
124
+ - **Headless one-shot drains only its own jobs.** `rubino -q` now emits and
125
+ flushes the JSON result envelope before draining, and scopes the post-turn job
126
+ drain to the run's own session, so a one-shot returns immediately even with a
127
+ background job backlog.
128
+ - **Subagent cards are distinguishable + carry the task id.** Concurrent
129
+ subagent cards label by a dimension drawn from the task prompt (rather than the
130
+ bare agent type), background "done" markers carry the task id, and the live
131
+ elapsed counter shows seconds (`1m05s`) so it visibly advances (#44, #570).
132
+ - **Pastes coalesce into a single placeholder**, input history is recalled and
133
+ persisted, `Enter` accepts the highlighted dropdown candidate, and
134
+ `task_result` running-polls no longer flood the transcript (#524, #525).
135
+ - **System-prompt grounding for control + tools.** The cap / continuation /
136
+ summary control is framed as trusted `[harness control]` so MiniMax-M3 stops
137
+ treating it as prompt-injection (#75); the background-shell lifecycle is primed
138
+ so the model uses `shell_output` / `shell_kill` correctly; the verification
139
+ step is scoped to never modify the environment and to stop honestly.
140
+ - **Memory-flush best-effort boundary** made airtight (#471), so a failure
141
+ flushing memory at shutdown can't take down the run.
142
+
143
+ ### Removed
144
+
145
+ - **Child→parent `ask_parent` / `answer_child` tools.** Subagents are
146
+ non-blocking background workers and can no longer pause mid-task to ask their
147
+ parent (or the human) a question; instead they make sensible default calls and
148
+ surface open decisions in their result. The two model-facing tools that
149
+ implemented that channel — `ask_parent` (the child→parent escalation) and
150
+ `answer_child` (the parent's reply) — are gone. The parent→child `steer` /
151
+ `probe` tools and the human approval gate (`/reply` for a child parked on an
152
+ approval) are unchanged. `tasks.ask_parent_timeout` is now vestigial.
153
+ - **`streaming.cursor` config key.** It was dead config (assigned, never read)
154
+ and is no longer accepted — remove it from any `config.yml`.
155
+ - **`security.require_confirmation_for_shell` config key.** Replaced by
156
+ `security.confirm_policy` (`dangerous_only` | `confirm_all`); the old key is no
157
+ longer honored.
158
+
159
+ ### Security
160
+
161
+ - **Hermes-style secret handling (#506).** Adopts the Hermes secret model across
162
+ the agent: the structured `read` tool blocks `.env` and credential files
163
+ outright, and secret **values** are redacted in the output of `read`, `grep`,
164
+ `shell` (including the live stream seam, not just the final buffer, #507),
165
+ `summarize`, and `read_attachment` (#511/#512). A `security.redact_secrets`
166
+ toggle (default **on**) controls redaction. The earlier per-read secret-file
167
+ approval gate was removed in favour of this block-list + redaction model
168
+ (#480). Over-broad redaction was then narrowed: the `ENV_ASSIGN` pattern is
169
+ anchored so `AUTHORS` / `SECRETARY` pass through while `API_KEY` / `AUTH_TOKEN`
170
+ still redact, the Telegram-token pattern is pinned to its canonical shape, and
171
+ fully-masked secrets carry an explicit marker rather than a bare `***`
172
+ (#67, #516).
173
+ - **Secrets are no longer persisted to memory (#99).** A `Security::SecretDetector`
174
+ is wired into the memory write path (it refuses an explicit save and the
175
+ auto-extract persist path) and into the redactor, catching prefixed key
176
+ shapes, prefix-less AWS secret keys, and a high-entropy heuristic — previously
177
+ an `sk-proj-…` key could be saved verbatim and re-injected into every future
178
+ system prompt.
179
+ - **Removed the dedicated `git` tool (RCE bypass).** Git now runs through the
180
+ hardened `shell` with strict arg parsing that rejects exec vectors
181
+ (`--ext-diff`, `-c`, textconv, …) plus a `GIT_HARDENED_ENV`, instead of a tool
182
+ that could be steered into arbitrary command execution (#536/#553).
183
+ - **Dangerous write/exec flag-forms prompt under the default gate (#61).**
184
+ `git -c` / `--output`, `sed -i`, `sort -o`, `find -delete` / `-exec`,
185
+ `tar --to-command`, `tee`, interpreter `-c` / `-e` / `--eval`, etc. no longer
186
+ auto-run under `dangerous_only`, while bare interpreters and read-only forms
187
+ still auto-run. A shared `Security::CommandNormalizer` also closes
188
+ line-continuation evasion (e.g. `rm -r\<newline>f` no longer slips past the
189
+ danger/approval layer).
190
+ - **Extended HOME credential read-block.** Reading credential stores under HOME
191
+ is blocked and a base64-decode-pipe-to-shell (`echo … | base64 -d | sh`) is
192
+ flagged dangerous (#519); the denylist now covers `.ssh`, `.aws`, `.netrc`,
193
+ `.git-credentials`, `.kube`, `.docker`, `.gnupg`, `.azure`, and `.config/gh`
194
+ (#537). A write through a **dangling in-workspace symlink** can no longer
195
+ escape the sandbox — the link target is resolved before the create-new-file
196
+ fallback (#62).
197
+ - **Tighten the `ruby_llm` floor to `>= 1.16` (#508).** The adapter wires native
198
+ providers through ruby_llm's generic `<provider>_api_base=` setters
199
+ (deepseek/mistral/etc., #482), which only exist from ruby_llm 1.16.0. The
200
+ gemspec previously allowed `~> 1.0`, so a fresh `gem install` could resolve
201
+ ruby_llm 1.15 and crash at runtime with `NoMethodError`. The dependency is now
202
+ `>= 1.16, < 2.0`.
203
+ - **Secret masking on `config set`.** `rubino config set` now masks the echoed
204
+ value when the key looks secret (`api_key`, `token`, `password`, `secret`,
205
+ `authorization`, …) and when the value itself contains inline credentials
206
+ (`key=value`, `Bearer …`, URL userinfo, `curl -u`, `mysql -p…`), so keys are
207
+ not printed in the clear to the terminal/scrollback.
208
+ - **Sanitized untrusted text rendered to the terminal (CWE-150).** Text that
209
+ originates from the model, tools, or filenames (subagent cards, `/`-palette and
210
+ `@`-picker menu labels, and the remaining CLI aside sinks — probe, reasoning,
211
+ open-fence, branch title) is now defanged of ANSI/OSC escape sequences before
212
+ it is written, closing an escape-injection class (#563/#564/#565–#568).
213
+ - **Vision egress hardening.** The `vision` tool now honours
214
+ `attachments.policy.aux_vision_egress` (default `true`): set it to `false` and
215
+ the tool refuses to send an image to an external auxiliary model, returning a
216
+ clean error instead of egressing the bytes (#578). Before any egress it also
217
+ **content-sniffs** the file (magic bytes win over the extension, fail-closed),
218
+ so a mislabelled or non-image file can't be smuggled to the external host
219
+ (#579).
220
+ - **OS sandbox covers more executors.** The OS write-jail (Landlock / Seatbelt)
221
+ now also confines background shells, `ruby`, and `run_tests`, with relaxation
222
+ gated on verified enforcement; a write-jail `EACCES` outside the workspace
223
+ produces an attributable "blocked by write-jail" hint (#74).
224
+
225
+ ### Fixed
226
+
227
+ - **MiniMax-M3 pre-tool-call "freeze".** Thinking/reasoning now defaults ON for
228
+ every provider (it was deliberately off for MiniMax-family ids). On the
229
+ anthropic-compatible path rubino now sends `thinking: {type: enabled,
230
+ budget_tokens: …}` and streams the model's reasoning deltas — so the multi-
231
+ second window where M3 reasons toward a tool-call is filled with visible
232
+ streamed reasoning instead of dead air (the symptom that read as the agent
233
+ "freezing" when it spawned subagents). Matches the reference agent's default
234
+ `reasoning_effort: medium`. A backend that rejects the budget is caught and
235
+ retried once without it (#75), so default-on is safe; set
236
+ `providers.<name>.supports_thinking: false` to opt out.
237
+ - **MCP `degraded` server state.** `/mcp` and `rubino doctor` now distinguish a
238
+ reachable server (`●`) from a **degraded** one (`⚠` — the process is alive but
239
+ a protocol call such as `tools/list` failed), instead of reporting it as plain
240
+ reachable (#575).
241
+ - **Session-title length cap.** A renamed session title is now length-capped at
242
+ rename and truncated on render, so an over-long title can't disrupt status /
243
+ session-list layout (#581).
244
+ - **Streaming fidelity.** A streaming turn no longer re-executes or re-surfaces
245
+ tool calls it already ran (no double "started" line or duplicate final tool)
246
+ (#53), and a split think/fence sentinel is held across the message-boundary
247
+ flush so reasoning no longer leaks into the body and prose isn't torn apart
248
+ (#43/#54). A committed markdown table glued to trailing prose no longer leaks
249
+ raw pipes, and a too-wide table fits the pane instead of tearing the border.
250
+ - **Subagent / multiplexer UI.** A running `blocked_on_parent` sub stays visible
251
+ in the footer while listed; cap-rejected delegation renders a neutral
252
+ "at capacity" row instead of a phantom failed card; the close-row / replay use
253
+ the per-call subagent name instead of a shared stale one (#35); the agent
254
+ picker opens reliably on `↓` and `←`/`↑` backs out; picking `◂ main` returns to
255
+ main immediately mid-turn; a nested child's menu no longer crashes it; and the
256
+ parent autonomously resumes at idle when background subagents finish while
257
+ detached (#37, #44, #51, #561).
258
+ - **Interrupt handling.** `Esc` at the tool-dispatch boundary raises a clean
259
+ interrupt instead of a malformed continuation that the backend rejects as
260
+ "invalid params"; a stray `Ctrl-C` exits cleanly (130) with no raw `net/http`
261
+ backtrace; and a background thread never dumps a backtrace on death.
262
+ - **Input papercuts.** Backspace (`DEL 0x7f`) deletes instead of inserting a
263
+ space (#522); a single `Ctrl-D` at an idle empty composer no longer hangs, and
264
+ fast input bursts coalesce their redraws (#520). Several composer
265
+ render/input races and resize-while-typing reflows that duplicated the
266
+ in-progress input into the scrollback are fixed, including chained resizes and
267
+ the resize REPAINT path (#481/#485/#486/#499/#500/#501/#503).
268
+ - **`edit` no longer crashes on non-UTF-8 / binary buffers.** Fuzzy-match
269
+ normalization passes invalid-encoding bytes through verbatim (#47), atomic
270
+ writes are binmode'd so binary buffers never transcode (the intermittent
271
+ in-session edit crash on accented files) (#65), and `clean_slice` reinterprets
272
+ binary as UTF-8 rather than calling `.encode` (#58). A failed edit / read /
273
+ write now shows `✗` instead of a green `✓`.
274
+ - **Background jobs and shells.** The job queue drains reliably — stale `running`
275
+ rows are reclaimed after the lease expires (#76) and `ExtractMemoryJob` is
276
+ prioritized over `SummarizeSessionJob` so save→recall doesn't lag (#79);
277
+ finished background shells are retired with their buffer and exit status
278
+ retained, so `shell_output` / `shell_tail` / `shell_kill` stay reachable next
279
+ turn (#78); shell cancel no longer orphans the child process group, and a
280
+ finished background shell auto-wakes the model.
281
+ - **Turn-ledger honesty.** Blocked / errored tools no longer count toward the
282
+ "N tools ran / M edits" ledger, so a turn whose only tool was refused stops
283
+ telling you to review nonexistent changes; the force-summary and closing-summary
284
+ nudges are grounded in the truthful turn ledger so the model can't confabulate
285
+ having done nothing (#36/#84). MiniMax HTTP 429 / quota errors are categorized
286
+ as retryable rate-limit (honouring `Retry-After`) instead of "Invalid request",
287
+ and the anti-confabulation note no longer over-fires on accurate local caveats.
288
+ - **Sessions / resume / doctor.** A per-session `flock` guard stops a concurrent
289
+ `--continue` from forking a moving transcript (#543), replay renders only the
290
+ new tail of a restated final message (#542), `--resume <id>` is validated
291
+ before the boot banner (#521), and `doctor` warns instead of false-green when
292
+ no usable credential exists and no longer implies an unverified key is
293
+ validated (#541/#546).
294
+ - **Non-native provider wiring (#482).** Fixed the preflight that falsely
295
+ reported non-native providers (deepseek/mistral/…) as ready; they are now
296
+ wired through the generic `<provider>_api_base=` setters and the run stops
297
+ on an unreachable endpoint instead of failing later. Transient name-resolution
298
+ failures (`EAI_AGAIN`) are retried rather than fatal, and a stream that ends
299
+ without a finish signal is recovered instead of failing the turn.
300
+ - **Parent-death reaps child shells (#478).** When the agent process dies, the
301
+ long-running child shells it spawned are reaped instead of being orphaned,
302
+ using a trap-safe SIGTERM/SIGHUP handler (no `Mutex` inside the signal trap).
303
+ - **Compaction no-op loop (#484).** Stopped a busy-loop on an over-budget
304
+ session that has too few messages to compact. The `doom_loop.threshold`
305
+ default is also no longer rejected by its own validator (#60).
306
+ - **Memory polish indicator no longer flashes every turn (#59).** The polish
307
+ worker starts only when a row was actually enqueued, the indicator composes
308
+ alongside the ctx bar instead of replacing it, and a verbatim repeat
309
+ short-circuits to the existing row at the write seam.
310
+ - **`/exit` and exit codes.** `/exit` routes through the quit-guard, and an
311
+ interactive session exits non-zero on an auth/credential error (#154).
312
+ - **CLI DX papercuts.** Fixed the bare-`rubino "prompt"` one-shot path, help-
313
+ session clutter, a bare-prompt did-you-mean edge case, and a `read_attachment`
314
+ hint that suggested markitdown for raster images instead of OCR.
315
+ - **Input hardening.** Fixed a raw SQLite3 exception on session input with
316
+ hostile/NUL bytes (#498) and cleaned up `Errno` error messages on the failure
317
+ paths; tightened mcp args validation and assorted low-severity
318
+ config/sessions/resume/CLI papercuts.
319
+
320
+ ## [0.5.0] - 2026-06-15
321
+
322
+ ### Added
323
+
324
+ - **One-shot tool-activity trace.** The non-interactive text path (`rubino
325
+ prompt` / `-q` / piped `chat`) now prints a concise per-tool activity trace
326
+ by default — one line per tool completion (`· edit foo.rb`, `· bash npm
327
+ test`) — routed to STDERR so the final answer on STDOUT stays clean
328
+ (`x=$(rubino prompt …)` captures only the answer). `--quiet`/`-Q` silences
329
+ the trace (machine-silent path); `--verbose`/`-v` widens each line's args.
330
+ `--output-format json`/`stream-json` (structured events on stdout) and the
331
+ interactive TUI tool-cards are unchanged. Mirrors the Codex/gemini-cli/Hermes
332
+ stderr-trace norm (Hermes `-q` default / `-Q` quiet).
333
+
334
+ - **Prompt-cache breakpoints (`cache_control`).** The conversation now inserts
335
+ cache breakpoints so the stable prefix (system + tool schemas + prior turns)
336
+ is reused across round-trips, cutting input-token cost/latency.
337
+ - **Situational tool-schema gating.** Tool definitions sent to the model are
338
+ scoped to the situation instead of always shipping the full set, reducing
339
+ prompt size and accidental tool selection.
340
+ - **Primary-agent switching.** Switch the active primary agent inline with
341
+ `/<name>`, the `/agent` command, or `Tab`; `@` remains reserved for file
342
+ references.
343
+ - **Detached post-turn polishing.** A post-turn polishing pass runs detached and
344
+ is cancellable with `Esc`, so it never blocks the next prompt.
345
+ - **Stdin pipe for one-shot.** Piped stdin is consumed as the prompt for
346
+ one-shot runs (`echo … | rubino prompt`), enabling unix-style composition.
347
+ - **Per-round-trip loop accounting.** Round-trips are counted, usage is summed
348
+ across them, and `tool_calls` are persisted on the streaming path.
349
+ - **Machine-readable headless output (`--output-format json | stream-json`, #312).**
350
+ `rubino prompt` / `chat -q` can now emit Claude-Code-aligned JSON for
351
+ CI/automation instead of prose. `--output-format json` (or the `--json` alias)
352
+ prints a single `{type:"result", subtype, is_error, result, session_id,
353
+ exit_reason, num_turns, duration_ms, usage:{input/output/cache_* tokens},
354
+ total_cost_usd, model}` object on stdout at completion; `--output-format
355
+ stream-json` emits JSONL (a `system`/`init` line, then Messages-API-shaped
356
+ `assistant`/`user` step objects, then the same final `result`). In both modes
357
+ ALL JSON goes to stdout and ALL logs/diagnostics/errors to stderr, and markdown
358
+ rendering is suppressed. The fail-closed / exit-code contract is preserved: a
359
+ blocked tool still emits the result with `is_error:true` and a non-zero exit.
360
+ The schema lives in a single shared serializer (`Rubino::Output::ResultSerializer`)
361
+ so it never drifts. `text` (default) is unchanged.
362
+ - **Higher tool-loop budget with an interactive extension prompt (#399).** The
363
+ `max_tool_iterations` default is raised from 8 to 25 so longer agent runs no
364
+ longer hit the cap mid-task. When the cap is reached interactively, the run
365
+ pauses with a budget-extension prompt — **Continue +N** (grant another batch),
366
+ **Summarize** (wrap up with what's done), or **Abort** — instead of failing
367
+ silently; headless runs keep the force-summarize behavior.
368
+ - **TUI: Ctrl-L clear-screen and a resize-while-typing fix (#395 / #401).**
369
+ `Ctrl-L` now clears the screen from the composer. Fixed a bug where resizing
370
+ the terminal while typing reflowed and duplicated the in-progress input into
371
+ the scrollback.
372
+
373
+ ### Security
374
+
375
+ - **Hardened/narrowed the command-allowlist convenience layer (SEC-R2-1/2/3).**
376
+ Closes three default-config / bare-`git` paths that could run arbitrary code
377
+ or write arbitrary files past the headless gate **without `--yolo`**:
378
+ - removed code-loading test/build runners (`bundle exec rspec`, …) from the
379
+ **shipped default** `command_allowlist` — they load and execute arbitrary
380
+ project code by design (`rspec -r FILE`), so they are not safely
381
+ auto-approvable (SEC-R2-3);
382
+ - an allowlisted **git** head is now vetted for GLOBAL flags before the
383
+ subcommand (`git -c alias.x='!cmd' x`, `-c core.sshCommand=…`, `-C dir`,
384
+ `--exec-path`) and for code-loading/mutating subcommands (`apply`, `am`,
385
+ `push`, hooks, …); the "approve git always" path now persists only a
386
+ narrowed `git <read-only verb>`, never bare `git` (SEC-R2-1);
387
+ - any allowlisted head whose argument is itself a program
388
+ (`awk`/`sed`/`perl`/`python`/`ruby`/`node`/`tar`/`tee`/`xargs`/shells) is
389
+ default-denied auto-approval, and write flags on read heads (`sort -o`, …)
390
+ are rejected (SEC-R2-2).
391
+
392
+ An allowlist is a convenience layer, **not** a security boundary (per industry
393
+ practice the OS sandbox is the real floor, tracked separately); this narrows
394
+ it to close the above default-config and bare-`git` RCEs.
395
+
396
+ ### Hardening
397
+
398
+ Four adversarial QA rounds fixed ~45 issues across the agent. Highlights:
399
+
400
+ - **Security.** Hardline-floor canonicalization; OOXML zip-bomb total-archive
401
+ cap; CWE-150 argument sanitization; threat-scanner; tightened
402
+ command-allowlist (see above).
403
+ - **Correctness.** UTF-8-safe edits; atomic compaction with auto-switch-to-child;
404
+ resume keeps the full tool history; cwd-scoped sessions; corrupt-DB recovery
405
+ (incl. `NotADatabaseException`); job-queue compare-and-swap; headless job drain
406
+ so memory works in automation.
407
+ - **Interrupt.** True cancel — stream cancellation with the partial persisted;
408
+ clean one-shot `SIGINT`/`SIGTERM` labels.
409
+ - **Performance.** Bounded huge-output memory; spill/paste eviction; streaming
410
+ grep with consistent ignore rules.
411
+ - **UX.** Config validation; `doctor` checks; resilient timeouts and error
412
+ classification.
413
+
414
+ Every fix was container-verified (non-root QA image, real MiniMax for live
415
+ behavior, true 0 failures); a full pre-release functionality sweep confirmed all
416
+ subsystems release-ready.
417
+
418
+ ## [0.4.1] - 2026-06-13
419
+
420
+ ### Security
421
+
422
+ - **Headless approvals now fail closed (#260).** A one-shot / scripted run
423
+ (`rubino prompt`, `chat -q`, no TTY) no longer auto-runs a tool that would
424
+ otherwise prompt: a write/edit, or a shell command not covered by your
425
+ `permissions` / command allowlist / read-only auto-allow, is **blocked, not
426
+ run**. A `blocked: <tool> needs approval …` line goes to stderr and the run
427
+ exits **2**, so CI/automation fails loudly instead of silently skipping (or
428
+ auto-executing). Full auto-exec now requires an explicit **`--yolo`** —
429
+ honored ONLY as a CLI flag, never grantable by a project-local/persisted
430
+ config — and **`--no-yolo`** forces fail-closed even over a yolo boot default.
431
+
432
+ ### Fixed — installer
433
+
434
+ - **`mise` method (#256)** alongside Homebrew and `rv`, with `global`/`local`
435
+ scope (`RUBINO_INSTALL_SCOPE`); `RUBINO_INSTALL_METHOD` now accepts `mise`.
436
+ - **Activation/PATH is persisted to your shell rc (#268)** (`.zshrc` /
437
+ `.bashrc` / `.profile`) and a **post-install fresh-shell gate** fails loudly
438
+ if `rubino` isn't on PATH in a new shell. `RUBINO_NO_MODIFY_RC=1` opts out.
439
+ - **`mise` installs pin the latest published gem version (#258/#268)** instead
440
+ of drifting to a pre-release / age-gated build.
441
+ - **Method-aware prereq preflight (#272)** (xz/git/toolchain) with real gem
442
+ error surfacing, and a **Debian-12 / glibc-too-old steer from rv → mise
443
+ (#241/#242/#272)** so users don't land on a broken musl Ruby.
444
+
445
+ ### Fixed
446
+
447
+ - **Config corruption + `doctor` crash on a scalar written over a section (#259).**
448
+ - **Streaming persistence (#266):** pre-tool narration is persisted and the
449
+ `tool_calls` audit is populated.
450
+ - **TUI render (#269):** table columns sized to content, nested/markdown fences
451
+ consumed, interrupt "ghost" line cleared.
452
+ - **Memory extraction bounded by a per-session cursor (#249)** — no more
453
+ re-scanning the whole transcript every turn.
454
+ - **Boots under a bare C/POSIX locale (#273)** without
455
+ `Encoding::CompatibilityError`.
456
+ - **Session summary folded into the single system message (#253/#254).**
4
457
 
5
458
  ## [0.4.0] - 2026-06-13
6
459
 
data/CONTRIBUTING.md CHANGED
@@ -21,11 +21,20 @@ Run the CLI from the checkout with `bundle exec rubino <command>`.
21
21
  ## Tests
22
22
 
23
23
  ```bash
24
- bundle exec rspec # full suite
24
+ bundle exec rspec # full suite (sequential; generates the coverage report)
25
25
  bundle exec rspec path/to/file_spec.rb
26
26
  bundle exec rake # default task == spec
27
+ bundle exec rake parallel:spec # full suite across all CPU cores (no coverage report)
28
+ bundle exec rake parallel:spec[4] # ...forced to 4 workers
27
29
  ```
28
30
 
31
+ `parallel:spec` shards the suite across one process per core via the
32
+ `parallel_tests` gem; each worker is isolated by `TEST_ENV_NUMBER`
33
+ (per-worker `RUBINO_HOME`, document fixtures, and example-status file).
34
+ SimpleCov is skipped under parallel runs (the workers would race the
35
+ coverage resultset) — use the sequential `bundle exec rspec` when you need
36
+ the coverage report.
37
+
29
38
  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
39
 
31
40
  ## Lint