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.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +12 -2
- data/AGENTS.md +1 -1
- data/CHANGELOG.md +454 -1
- data/CONTRIBUTING.md +10 -1
- data/README.md +69 -11
- data/Rakefile +48 -0
- data/docs/agents.md +82 -48
- data/docs/architecture.md +4 -11
- data/docs/commands.md +46 -7
- data/docs/configuration.md +174 -30
- data/docs/getting-started.md +5 -3
- data/docs/mcp.md +3 -3
- data/docs/memory.md +3 -3
- data/docs/security.md +17 -6
- data/docs/tools.md +45 -49
- data/docs/troubleshooting.md +1 -1
- data/exe/rubino +16 -2
- data/ext/landlock/extconf.rb +78 -0
- data/ext/landlock/landlock.c +253 -0
- data/install.sh +715 -54
- data/lib/rubino/active_agent.rb +73 -0
- data/lib/rubino/agent/action_claim_guard.rb +913 -0
- data/lib/rubino/agent/agent_registry.rb +5 -2
- data/lib/rubino/agent/definition.rb +4 -28
- data/lib/rubino/agent/fallback_chain.rb +0 -6
- data/lib/rubino/agent/iteration_budget.rb +109 -3
- data/lib/rubino/agent/loop.rb +664 -42
- data/lib/rubino/agent/model_call_runner.rb +81 -3
- data/lib/rubino/agent/prompts/build.txt +55 -7
- data/lib/rubino/agent/prompts/general.txt +8 -3
- data/lib/rubino/agent/response_validator.rb +8 -0
- data/lib/rubino/agent/runner.rb +307 -13
- data/lib/rubino/agent/tool_executor.rb +368 -31
- data/lib/rubino/agent/truncation_continuation.rb +11 -5
- data/lib/rubino/api/operations/approvals/decide_operation.rb +0 -4
- data/lib/rubino/api/operations/clarifications/decide_operation.rb +0 -4
- data/lib/rubino/api/operations/cron_jobs/create_operation.rb +0 -4
- data/lib/rubino/api/operations/cron_jobs/delete_operation.rb +0 -4
- data/lib/rubino/api/operations/cron_jobs/list_operation.rb +0 -4
- data/lib/rubino/api/operations/cron_jobs/pause_operation.rb +1 -5
- data/lib/rubino/api/operations/cron_jobs/resume_operation.rb +1 -5
- data/lib/rubino/api/operations/cron_jobs/show_operation.rb +0 -4
- data/lib/rubino/api/operations/cron_jobs/trigger_operation.rb +0 -4
- data/lib/rubino/api/operations/cron_jobs/update_operation.rb +0 -4
- data/lib/rubino/api/operations/files/read_operation.rb +1 -5
- data/lib/rubino/api/operations/files/upload_operation.rb +0 -4
- data/lib/rubino/api/operations/health_operation.rb +1 -5
- data/lib/rubino/api/operations/memory/delete_operation.rb +0 -4
- data/lib/rubino/api/operations/memory/index_operation.rb +0 -4
- data/lib/rubino/api/operations/memory/stats_operation.rb +0 -4
- data/lib/rubino/api/operations/metrics_operation.rb +1 -1
- data/lib/rubino/api/operations/mode/show_operation.rb +0 -4
- data/lib/rubino/api/operations/mode/update_operation.rb +0 -4
- data/lib/rubino/api/operations/models/list_operation.rb +0 -4
- data/lib/rubino/api/operations/oauth/connections/disconnect_operation.rb +0 -4
- data/lib/rubino/api/operations/oauth/connections/list_operation.rb +0 -4
- data/lib/rubino/api/operations/oauth/providers/callback_operation.rb +0 -4
- data/lib/rubino/api/operations/oauth/providers/connect_operation.rb +0 -4
- data/lib/rubino/api/operations/oauth/providers/list_operation.rb +0 -4
- data/lib/rubino/api/operations/runs/create_operation.rb +0 -4
- data/lib/rubino/api/operations/runs/events_operation.rb +0 -4
- data/lib/rubino/api/operations/runs/stop_operation.rb +0 -4
- data/lib/rubino/api/operations/sessions/create_operation.rb +0 -4
- data/lib/rubino/api/operations/sessions/delete_operation.rb +0 -4
- data/lib/rubino/api/operations/sessions/index_operation.rb +0 -4
- data/lib/rubino/api/operations/sessions/retry_operation.rb +0 -4
- data/lib/rubino/api/operations/sessions/show_operation.rb +0 -4
- data/lib/rubino/api/operations/sessions/undo_operation.rb +0 -4
- data/lib/rubino/api/operations/skills/list_operation.rb +0 -4
- data/lib/rubino/api/operations/skills/toggle_operation.rb +0 -4
- data/lib/rubino/api/operations/tasks/index_operation.rb +0 -4
- data/lib/rubino/api/operations/tasks/show_operation.rb +0 -4
- data/lib/rubino/api/operations/tasks/stop_operation.rb +0 -4
- data/lib/rubino/api/router.rb +2 -2
- data/lib/rubino/api/server.rb +19 -0
- data/lib/rubino/attachments/policy.rb +8 -0
- data/lib/rubino/attachments/preamble.rb +16 -8
- data/lib/rubino/boot/config_guard.rb +71 -0
- data/lib/rubino/cli/chat/completion_builder.rb +44 -8
- data/lib/rubino/cli/chat/idle_card_host.rb +7 -1
- data/lib/rubino/cli/chat/session_resolver.rb +186 -50
- data/lib/rubino/cli/chat_command.rb +1724 -91
- data/lib/rubino/cli/commands.rb +373 -1
- data/lib/rubino/cli/config_command.rb +118 -11
- data/lib/rubino/cli/doctor_command.rb +268 -23
- data/lib/rubino/cli/jobs_command.rb +42 -3
- data/lib/rubino/cli/memory_command.rb +76 -23
- data/lib/rubino/cli/onboarding_wizard.rb +85 -7
- data/lib/rubino/cli/server_command.rb +43 -1
- data/lib/rubino/cli/session_command.rb +272 -18
- data/lib/rubino/cli/setup_command.rb +293 -8
- data/lib/rubino/cli/skills_command.rb +88 -20
- data/lib/rubino/cli/trust_gate.rb +16 -7
- data/lib/rubino/commands/built_ins.rb +4 -2
- data/lib/rubino/commands/command.rb +12 -2
- data/lib/rubino/commands/executor.rb +161 -19
- data/lib/rubino/commands/handlers/agent_switch.rb +100 -0
- data/lib/rubino/commands/handlers/agents.rb +324 -60
- data/lib/rubino/commands/handlers/config.rb +8 -1
- data/lib/rubino/commands/handlers/display.rb +50 -0
- data/lib/rubino/commands/handlers/help.rb +106 -14
- data/lib/rubino/commands/handlers/mcp.rb +7 -32
- data/lib/rubino/commands/handlers/memory.rb +23 -38
- data/lib/rubino/commands/handlers/sessions.rb +70 -33
- data/lib/rubino/commands/handlers/skills.rb +47 -28
- data/lib/rubino/commands/handlers/status.rb +65 -10
- data/lib/rubino/commands/loader.rb +12 -0
- data/lib/rubino/compression/compression_result.rb +35 -0
- data/lib/rubino/compression/compressor.rb +109 -0
- data/lib/rubino/compression/content_router.rb +240 -0
- data/lib/rubino/compression/diff_compressor.rb +252 -0
- data/lib/rubino/compression/javascript_code_skeleton.rb +15 -0
- data/lib/rubino/compression/json_compressor.rb +274 -0
- data/lib/rubino/compression/line_skeleton.rb +92 -0
- data/lib/rubino/compression/log_compressor.rb +299 -0
- data/lib/rubino/compression/python_code_skeleton.rb +122 -0
- data/lib/rubino/compression/ruby_code_skeleton.rb +80 -0
- data/lib/rubino/compression/tree_sitter_code_skeleton.rb +118 -0
- data/lib/rubino/compression/tsx_code_skeleton.rb +15 -0
- data/lib/rubino/compression/typescript_code_skeleton.rb +15 -0
- data/lib/rubino/config/configuration.rb +151 -105
- data/lib/rubino/config/defaults.rb +369 -41
- data/lib/rubino/config/loader.rb +71 -13
- data/lib/rubino/config/reasoning_prefs.rb +23 -0
- data/lib/rubino/config/validator.rb +384 -0
- data/lib/rubino/config/writer.rb +123 -31
- data/lib/rubino/context/compressor.rb +185 -23
- data/lib/rubino/context/file_discovery.rb +0 -8
- data/lib/rubino/context/message_boundary.rb +26 -5
- data/lib/rubino/context/project_languages.rb +83 -0
- data/lib/rubino/context/prompt_assembler.rb +110 -22
- data/lib/rubino/context/summary_builder.rb +77 -27
- data/lib/rubino/context/token_budget.rb +38 -13
- data/lib/rubino/context/token_estimate.rb +45 -0
- data/lib/rubino/context/tool_result_pruner.rb +81 -0
- data/lib/rubino/database/connection.rb +154 -3
- data/lib/rubino/database/migrations/001_create_initial_schema.rb +314 -40
- data/lib/rubino/database/migrator.rb +81 -14
- data/lib/rubino/documents/cap_exceeded.rb +13 -0
- data/lib/rubino/documents/converters/csv.rb +4 -3
- data/lib/rubino/documents/converters/docx.rb +29 -5
- data/lib/rubino/documents/converters/html.rb +5 -1
- data/lib/rubino/documents/converters/json.rb +2 -1
- data/lib/rubino/documents/converters/pdf.rb +11 -2
- data/lib/rubino/documents/converters/plain.rb +2 -1
- data/lib/rubino/documents/converters/pptx.rb +11 -2
- data/lib/rubino/documents/converters/xlsx.rb +35 -4
- data/lib/rubino/documents/converters/xml.rb +2 -1
- data/lib/rubino/documents/limits.rb +210 -0
- data/lib/rubino/documents.rb +10 -3
- data/lib/rubino/errors.rb +36 -5
- data/lib/rubino/files/workspace.rb +2 -2
- data/lib/rubino/interaction/cancel_token.rb +19 -3
- data/lib/rubino/interaction/events.rb +13 -3
- data/lib/rubino/interaction/input_queue.rb +11 -0
- data/lib/rubino/interaction/lifecycle.rb +238 -33
- data/lib/rubino/interaction/polishing.rb +184 -0
- data/lib/rubino/interaction/probe.rb +1 -1
- data/lib/rubino/jobs/cron_job_repository.rb +5 -12
- data/lib/rubino/jobs/handlers/cleanup_sessions_job.rb +11 -0
- data/lib/rubino/jobs/handlers/distill_skill_job.rb +67 -21
- data/lib/rubino/jobs/queue.rb +133 -13
- data/lib/rubino/jobs/runner.rb +24 -6
- data/lib/rubino/jobs/worker.rb +1 -5
- data/lib/rubino/llm/adapter_factory.rb +1 -1
- data/lib/rubino/llm/adapter_response.rb +47 -4
- data/lib/rubino/llm/auxiliary_client.rb +63 -3
- data/lib/rubino/llm/cache_breakpoint_middleware.rb +194 -0
- data/lib/rubino/llm/credential_check.rb +76 -20
- data/lib/rubino/llm/error_classifier.rb +186 -77
- data/lib/rubino/llm/fake_provider.rb +3 -3
- data/lib/rubino/llm/inline_think_filter.rb +103 -15
- data/lib/rubino/llm/reasoning_manager.rb +3 -26
- data/lib/rubino/llm/request.rb +26 -15
- data/lib/rubino/llm/ruby_llm_adapter.rb +623 -67
- data/lib/rubino/llm/scenario_loader.rb +10 -17
- data/lib/rubino/llm/scenarios/glued-table-prose.yml +36 -0
- data/lib/rubino/llm/scenarios/growing-table.yml +49 -0
- data/lib/rubino/llm/scenarios/narrow-terminal-table.yml +47 -0
- data/lib/rubino/llm/scenarios/streamed-table.yml +55 -0
- data/lib/rubino/llm/scenarios/table-then-prose.yml +34 -0
- data/lib/rubino/llm/scenarios/too-wide-table.yml +47 -0
- data/lib/rubino/llm/scenarios/wide-table.yml +1 -1
- data/lib/rubino/llm/thinking_support.rb +17 -12
- data/lib/rubino/llm/tool_bridge.rb +200 -32
- data/lib/rubino/mcp/manager.rb +71 -10
- data/lib/rubino/mcp/mcp_tool_wrapper.rb +38 -3
- data/lib/rubino/memory/aux_retry.rb +107 -0
- data/lib/rubino/memory/backends/sqlite.rb +104 -67
- data/lib/rubino/memory/backends.rb +26 -10
- data/lib/rubino/memory/deduplicator.rb +22 -0
- data/lib/rubino/memory/flusher.rb +35 -1
- data/lib/rubino/memory/salience_gate.rb +129 -0
- data/lib/rubino/memory/sqlite_extraction.rb +70 -0
- data/lib/rubino/memory/sqlite_extraction_prompt.rb +16 -1
- data/lib/rubino/memory/store.rb +48 -20
- data/lib/rubino/memory/threat_scanner.rb +60 -0
- data/lib/rubino/memory.rb +47 -0
- data/lib/rubino/oauth/provider.rb +0 -5
- data/lib/rubino/output/cost.rb +52 -0
- data/lib/rubino/output/headless_block_latch.rb +53 -0
- data/lib/rubino/output/result_serializer.rb +222 -0
- data/lib/rubino/output/turn_recorder.rb +77 -0
- data/lib/rubino/run/event_store.rb +1 -6
- data/lib/rubino/run/repository.rb +0 -14
- data/lib/rubino/security/approval_policy.rb +314 -33
- data/lib/rubino/security/command_allowlist.rb +79 -4
- data/lib/rubino/security/command_normalizer.rb +36 -0
- data/lib/rubino/security/dangerous_patterns.rb +17 -4
- data/lib/rubino/security/doom_loop_detector.rb +21 -2
- data/lib/rubino/security/hardline_guard.rb +190 -16
- data/lib/rubino/security/pattern_matcher.rb +28 -5
- data/lib/rubino/security/prefix_deriver.rb +25 -6
- data/lib/rubino/security/readonly_commands.rb +442 -18
- data/lib/rubino/security/redactor.rb +272 -0
- data/lib/rubino/security/sandbox.rb +460 -0
- data/lib/rubino/security/secret_detector.rb +110 -0
- data/lib/rubino/security/secret_path.rb +263 -0
- data/lib/rubino/security/url_safety.rb +255 -0
- data/lib/rubino/session/lock.rb +91 -0
- data/lib/rubino/session/message.rb +38 -3
- data/lib/rubino/session/picker.rb +95 -0
- data/lib/rubino/session/repository.rb +249 -31
- data/lib/rubino/session/store.rb +135 -21
- data/lib/rubino/skills/installer.rb +116 -32
- data/lib/rubino/skills/prompt_index.rb +2 -2
- data/lib/rubino/skills/registry.rb +56 -6
- data/lib/rubino/skills/skill.rb +94 -12
- data/lib/rubino/skills/skill_tool.rb +21 -25
- data/lib/rubino/skills/state_repository.rb +0 -4
- data/lib/rubino/tools/background_tasks.rb +299 -47
- data/lib/rubino/tools/base.rb +219 -4
- data/lib/rubino/tools/edit_tool.rb +116 -31
- data/lib/rubino/tools/fuzzy_match.rb +212 -0
- data/lib/rubino/tools/glob_tool.rb +52 -9
- data/lib/rubino/tools/grep_tool.rb +71 -11
- data/lib/rubino/tools/multi_edit_tool.rb +88 -20
- data/lib/rubino/tools/patch_tool.rb +56 -10
- data/lib/rubino/tools/probe_tool.rb +0 -20
- data/lib/rubino/tools/question_tool.rb +54 -2
- data/lib/rubino/tools/read_attachment_tool.rb +24 -12
- data/lib/rubino/tools/read_tool.rb +159 -35
- data/lib/rubino/tools/read_tracker.rb +189 -35
- data/lib/rubino/tools/registry.rb +151 -31
- data/lib/rubino/tools/result.rb +48 -9
- data/lib/rubino/tools/retrieve_output_tool.rb +70 -0
- data/lib/rubino/tools/ruby_tool.rb +0 -0
- data/lib/rubino/tools/shell_kill_tool.rb +6 -2
- data/lib/rubino/tools/shell_output_tool.rb +7 -1
- data/lib/rubino/tools/shell_registry.rb +229 -5
- data/lib/rubino/tools/shell_tail_tool.rb +6 -1
- data/lib/rubino/tools/shell_tool.rb +523 -54
- data/lib/rubino/tools/steer_tool.rb +2 -21
- data/lib/rubino/tools/subagent_probe.rb +1 -1
- data/lib/rubino/tools/summarize_file_tool.rb +12 -0
- data/lib/rubino/tools/task_result_tool.rb +8 -2
- data/lib/rubino/tools/task_stop_tool.rb +15 -22
- data/lib/rubino/tools/task_tool.rb +229 -104
- data/lib/rubino/tools/vision_tool.rb +37 -4
- data/lib/rubino/tools/webfetch_tool.rb +184 -7
- data/lib/rubino/tools/websearch_tool.rb +92 -30
- data/lib/rubino/tools/write_tool.rb +24 -5
- data/lib/rubino/ui/agent_menu.rb +179 -0
- data/lib/rubino/ui/api.rb +12 -3
- data/lib/rubino/ui/base.rb +13 -2
- data/lib/rubino/ui/bottom_composer.rb +1483 -203
- data/lib/rubino/ui/cli.rb +1340 -272
- data/lib/rubino/ui/completion_menu.rb +35 -50
- data/lib/rubino/ui/composer/input_line.rb +131 -0
- data/lib/rubino/ui/composer/subagent_panel.rb +35 -0
- data/lib/rubino/ui/headless_trace.rb +63 -0
- data/lib/rubino/ui/input_history.rb +90 -5
- data/lib/rubino/ui/live_region.rb +82 -7
- data/lib/rubino/ui/markdown_renderer.rb +214 -17
- data/lib/rubino/ui/menu_view.rb +117 -0
- data/lib/rubino/ui/notifier.rb +0 -2
- data/lib/rubino/ui/null.rb +53 -6
- data/lib/rubino/ui/paste_store.rb +49 -3
- data/lib/rubino/ui/printer_base.rb +135 -8
- data/lib/rubino/ui/queued_indicators.rb +6 -1
- data/lib/rubino/ui/status_bar.rb +61 -7
- data/lib/rubino/ui/streaming_markdown.rb +148 -6
- data/lib/rubino/ui/subagent_cards.rb +126 -25
- data/lib/rubino/ui/tool_label.rb +52 -0
- data/lib/rubino/update_check.rb +39 -4
- data/lib/rubino/util/atomic_file.rb +129 -0
- data/lib/rubino/util/duration.rb +8 -5
- data/lib/rubino/util/ignore_rules.rb +120 -0
- data/lib/rubino/util/output.rb +275 -13
- data/lib/rubino/util/secrets_mask.rb +70 -7
- data/lib/rubino/util/spill_store.rb +153 -0
- data/lib/rubino/version.rb +7 -1
- data/lib/rubino/workspace.rb +74 -3
- data/lib/rubino.rb +216 -25
- data/rubino-agent.gemspec +28 -1
- data/skills/ruby-expert/SKILL.md +1 -0
- metadata +116 -29
- data/docs/plugins.md +0 -195
- data/lib/rubino/agent/router.rb +0 -65
- data/lib/rubino/database/migrations/002_create_runs.rb +0 -45
- data/lib/rubino/database/migrations/003_create_skill_states.rb +0 -15
- data/lib/rubino/database/migrations/004_create_cron_jobs.rb +0 -36
- data/lib/rubino/database/migrations/005_create_oauth_connections.rb +0 -27
- data/lib/rubino/database/migrations/006_create_webhook_deliveries.rb +0 -34
- data/lib/rubino/database/migrations/007_create_messages_fts.rb +0 -59
- data/lib/rubino/database/migrations/008_create_memory_facts.rb +0 -75
- data/lib/rubino/database/migrations/009_create_memory_graph.rb +0 -55
- data/lib/rubino/database/migrations/010_add_owner_pid_to_sessions.rb +0 -20
- data/lib/rubino/interaction/state.rb +0 -56
- data/lib/rubino/memory/backends/default.rb +0 -101
- data/lib/rubino/memory/extractor.rb +0 -85
- data/lib/rubino/memory/retriever.rb +0 -50
- data/lib/rubino/plugins/registry.rb +0 -75
- data/lib/rubino/plugins.rb +0 -86
- data/lib/rubino/tools/answer_child_tool.rb +0 -83
- data/lib/rubino/tools/ask_parent_tool.rb +0 -232
- data/lib/rubino/tools/git_tool.rb +0 -71
- data/lib/rubino/tools/github_tool.rb +0 -233
- data/lib/rubino/tools/test_tool.rb +0 -454
- data/lib/rubino/ui/subagent_view.rb +0 -266
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1debe685b923c625e0dc4dcf95da3c9fc12fcd6c73bdff71e35164279e62b06
|
|
4
|
+
data.tar.gz: 5451e122fc13bfdd4ffeba0e680cad9fb6b976dfabe8f9dcfd894e5215ac9688
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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:
|
|
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
|
-
## [
|
|
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
|