rubik 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/ATTRIBUTION.md +139 -0
- data/CHANGELOG.md +236 -0
- data/LICENSE +21 -0
- data/README.md +269 -0
- data/bin/rubik +82 -0
- data/lib/rubik/adapter/base.rb +195 -0
- data/lib/rubik/adapter/c.rb +36 -0
- data/lib/rubik/adapter/crystal.rb +78 -0
- data/lib/rubik/adapter/generic.rb +86 -0
- data/lib/rubik/adapter/go.rb +36 -0
- data/lib/rubik/adapter/node.rb +146 -0
- data/lib/rubik/adapter/process.rb +78 -0
- data/lib/rubik/adapter/python.rb +30 -0
- data/lib/rubik/adapter/registry.rb +81 -0
- data/lib/rubik/adapter/ruby.rb +30 -0
- data/lib/rubik/adapter/rust.rb +36 -0
- data/lib/rubik/adapter/zig.rb +36 -0
- data/lib/rubik/adapter.rb +18 -0
- data/lib/rubik/ast/body_loader.rb +19 -0
- data/lib/rubik/ast/c_extractor.rb +114 -0
- data/lib/rubik/ast/cache_store.rb +191 -0
- data/lib/rubik/ast/extractor.rb +59 -0
- data/lib/rubik/ast/fallback_cleaner.rb +145 -0
- data/lib/rubik/ast/go_extractor.rb +148 -0
- data/lib/rubik/ast/graph.rb +272 -0
- data/lib/rubik/ast/python_extractor.rb +120 -0
- data/lib/rubik/ast/ruby_extractor.rb +260 -0
- data/lib/rubik/ast/rust_extractor.rb +127 -0
- data/lib/rubik/ast/symbol.rb +103 -0
- data/lib/rubik/ast/treesitter/base_walker.rb +109 -0
- data/lib/rubik/ast/treesitter/c_walker.rb +122 -0
- data/lib/rubik/ast/treesitter/go_walker.rb +120 -0
- data/lib/rubik/ast/treesitter/python_walker.rb +107 -0
- data/lib/rubik/ast/treesitter/rust_walker.rb +130 -0
- data/lib/rubik/ast/treesitter/typescript_walker.rb +182 -0
- data/lib/rubik/ast/treesitter/zig_walker.rb +109 -0
- data/lib/rubik/ast/treesitter_extractor.rb +93 -0
- data/lib/rubik/ast/typescript_extractor.rb +283 -0
- data/lib/rubik/ast/zig_extractor.rb +116 -0
- data/lib/rubik/ast.rb +13 -0
- data/lib/rubik/benchmark/runner.rb +224 -0
- data/lib/rubik/benchmark/synth_repo.rb +59 -0
- data/lib/rubik/benchmark.rb +9 -0
- data/lib/rubik/buffer/diff.rb +43 -0
- data/lib/rubik/buffer/manager.rb +427 -0
- data/lib/rubik/buffer/path_validator.rb +60 -0
- data/lib/rubik/buffer/snapshot.rb +99 -0
- data/lib/rubik/buffer.rb +10 -0
- data/lib/rubik/coeffects.rb +129 -0
- data/lib/rubik/concurrency/boundary.rb +74 -0
- data/lib/rubik/concurrency/dns_pool.rb +68 -0
- data/lib/rubik/concurrency/errors.rb +17 -0
- data/lib/rubik/concurrency/event_loop.rb +441 -0
- data/lib/rubik/concurrency/join.rb +55 -0
- data/lib/rubik/concurrency/process_waiter.rb +55 -0
- data/lib/rubik/concurrency/scheduler.rb +84 -0
- data/lib/rubik/concurrency/timers.rb +109 -0
- data/lib/rubik/concurrency.rb +132 -0
- data/lib/rubik/config/deep_ops.rb +80 -0
- data/lib/rubik/config/defaults.rb +248 -0
- data/lib/rubik/config/model_catalog.rb +93 -0
- data/lib/rubik/config/store.rb +155 -0
- data/lib/rubik/config.rb +216 -0
- data/lib/rubik/context/git_info.rb +84 -0
- data/lib/rubik/context/git_lister.rb +37 -0
- data/lib/rubik/context/index.rb +134 -0
- data/lib/rubik/context/repo_context_reader.rb +121 -0
- data/lib/rubik/context/scanner.rb +439 -0
- data/lib/rubik/context.rb +11 -0
- data/lib/rubik/diagnostics/collector.rb +200 -0
- data/lib/rubik/diagnostics/entry.rb +60 -0
- data/lib/rubik/diagnostics/parser.rb +107 -0
- data/lib/rubik/diagnostics.rb +10 -0
- data/lib/rubik/event_bus.rb +56 -0
- data/lib/rubik/kernel/dispatcher.rb +171 -0
- data/lib/rubik/kernel/dmesg.rb +116 -0
- data/lib/rubik/kernel/init.rb +177 -0
- data/lib/rubik/kernel/record.rb +85 -0
- data/lib/rubik/kernel/unit.rb +160 -0
- data/lib/rubik/llm/anthropic.rb +202 -0
- data/lib/rubik/llm/chunk_assembler.rb +285 -0
- data/lib/rubik/llm/client.rb +526 -0
- data/lib/rubik/llm/formatter.rb +450 -0
- data/lib/rubik/llm/message_sanitizer.rb +87 -0
- data/lib/rubik/llm/ollama.rb +167 -0
- data/lib/rubik/llm/openai.rb +21 -0
- data/lib/rubik/llm/openai_compatible.rb +216 -0
- data/lib/rubik/llm/provider.rb +125 -0
- data/lib/rubik/llm/registry.rb +336 -0
- data/lib/rubik/llm/router.rb +100 -0
- data/lib/rubik/llm.rb +18 -0
- data/lib/rubik/log.rb +38 -0
- data/lib/rubik/lsp/client.rb +475 -0
- data/lib/rubik/lsp/manager.rb +366 -0
- data/lib/rubik/lsp.rb +9 -0
- data/lib/rubik/mcp/boot_lifecycle.rb +114 -0
- data/lib/rubik/mcp/client.rb +169 -0
- data/lib/rubik/mcp/errors.rb +11 -0
- data/lib/rubik/mcp/manager.rb +619 -0
- data/lib/rubik/mcp/proxy_tool.rb +104 -0
- data/lib/rubik/mcp/transport/stdio.rb +255 -0
- data/lib/rubik/mcp.rb +12 -0
- data/lib/rubik/orchestrator/activity_log.rb +75 -0
- data/lib/rubik/orchestrator/auto_apply.rb +166 -0
- data/lib/rubik/orchestrator/budget_manager.rb +97 -0
- data/lib/rubik/orchestrator/continuation_manager.rb +161 -0
- data/lib/rubik/orchestrator/finish_message_selector.rb +55 -0
- data/lib/rubik/orchestrator/goal_ledger/drift.rb +71 -0
- data/lib/rubik/orchestrator/goal_ledger/ledger.rb +399 -0
- data/lib/rubik/orchestrator/goal_ledger/observation_projection.rb +179 -0
- data/lib/rubik/orchestrator/goal_ledger/patch.rb +268 -0
- data/lib/rubik/orchestrator/goal_ledger/schema.rb +123 -0
- data/lib/rubik/orchestrator/goal_ledger/validator.rb +361 -0
- data/lib/rubik/orchestrator/goal_ledger/verifier.rb +129 -0
- data/lib/rubik/orchestrator/goal_ledger/verify_spec.rb +50 -0
- data/lib/rubik/orchestrator/goal_ledger.rb +65 -0
- data/lib/rubik/orchestrator/goal_state.rb +184 -0
- data/lib/rubik/orchestrator/goal_wait.rb +125 -0
- data/lib/rubik/orchestrator/loop.rb +388 -0
- data/lib/rubik/orchestrator/lsp_feedback.rb +55 -0
- data/lib/rubik/orchestrator/maestro_gate.rb +65 -0
- data/lib/rubik/orchestrator/provider_recovery.rb +90 -0
- data/lib/rubik/orchestrator/provider_resolver.rb +69 -0
- data/lib/rubik/orchestrator/shell_deps.rb +85 -0
- data/lib/rubik/orchestrator/step_executor.rb +328 -0
- data/lib/rubik/orchestrator/stream_manager.rb +118 -0
- data/lib/rubik/orchestrator/subagent/activation.rb +65 -0
- data/lib/rubik/orchestrator/subagent/budget.rb +108 -0
- data/lib/rubik/orchestrator/subagent/completion.rb +61 -0
- data/lib/rubik/orchestrator/subagent/concurrency_gate.rb +37 -0
- data/lib/rubik/orchestrator/subagent/conversation_id.rb +67 -0
- data/lib/rubik/orchestrator/subagent/dependency.rb +398 -0
- data/lib/rubik/orchestrator/subagent/instance.rb +384 -0
- data/lib/rubik/orchestrator/subagent/lifecycle.rb +118 -0
- data/lib/rubik/orchestrator/subagent/messaging.rb +61 -0
- data/lib/rubik/orchestrator/subagent/narration.rb +125 -0
- data/lib/rubik/orchestrator/subagent/notices.rb +209 -0
- data/lib/rubik/orchestrator/subagent/questioner.rb +80 -0
- data/lib/rubik/orchestrator/subagent/reaper.rb +98 -0
- data/lib/rubik/orchestrator/subagent/resilience.rb +70 -0
- data/lib/rubik/orchestrator/subagent/reuse.rb +127 -0
- data/lib/rubik/orchestrator/subagent/scoped_registry.rb +79 -0
- data/lib/rubik/orchestrator/subagent/step_runner.rb +240 -0
- data/lib/rubik/orchestrator/subagent/storage_id.rb +26 -0
- data/lib/rubik/orchestrator/subagent/supervisor.rb +327 -0
- data/lib/rubik/orchestrator/subagent/transcript_formatter.rb +141 -0
- data/lib/rubik/orchestrator/subagent/type_registry.rb +199 -0
- data/lib/rubik/orchestrator/subagent_notice_inbox.rb +80 -0
- data/lib/rubik/orchestrator/tool_call_id.rb +47 -0
- data/lib/rubik/orchestrator/tool_circuit_breaker.rb +91 -0
- data/lib/rubik/orchestrator/tool_history.rb +39 -0
- data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +102 -0
- data/lib/rubik/orchestrator/turn_executor/user_channel.rb +172 -0
- data/lib/rubik/orchestrator/turn_executor.rb +376 -0
- data/lib/rubik/orchestrator.rb +11 -0
- data/lib/rubik/platform/filesystem.rb +64 -0
- data/lib/rubik/platform/io_drain.rb +53 -0
- data/lib/rubik/platform/path.rb +210 -0
- data/lib/rubik/platform/process.rb +38 -0
- data/lib/rubik/platform/sanitize.rb +157 -0
- data/lib/rubik/platform.rb +113 -0
- data/lib/rubik/plugin/dsl.rb +51 -0
- data/lib/rubik/plugin/manager.rb +247 -0
- data/lib/rubik/plugin/storage.rb +195 -0
- data/lib/rubik/plugin.rb +10 -0
- data/lib/rubik/prompt/context_builder.rb +377 -0
- data/lib/rubik/prompt/context_compactor.rb +326 -0
- data/lib/rubik/prompt/conversation_rows.rb +74 -0
- data/lib/rubik/prompt/doctrine.rb +160 -0
- data/lib/rubik/prompt/emergency_compactor.rb +81 -0
- data/lib/rubik/prompt/services.rb +60 -0
- data/lib/rubik/prompt/token_estimator.rb +63 -0
- data/lib/rubik/prompt/tool_teachings.rb +78 -0
- data/lib/rubik/prompt.rb +13 -0
- data/lib/rubik/repair/loop.rb +95 -0
- data/lib/rubik/repair.rb +8 -0
- data/lib/rubik/repl/ast_warmup.rb +129 -0
- data/lib/rubik/repl/command_context.rb +214 -0
- data/lib/rubik/repl/command_parser.rb +48 -0
- data/lib/rubik/repl/commands/agent.rb +30 -0
- data/lib/rubik/repl/commands/agent_context.rb +289 -0
- data/lib/rubik/repl/commands/agent_execution.rb +342 -0
- data/lib/rubik/repl/commands/agent_models.rb +212 -0
- data/lib/rubik/repl/commands/agent_providers.rb +232 -0
- data/lib/rubik/repl/commands/base.rb +55 -0
- data/lib/rubik/repl/commands/buffer.rb +124 -0
- data/lib/rubik/repl/commands/dmesg.rb +87 -0
- data/lib/rubik/repl/commands/lsp.rb +235 -0
- data/lib/rubik/repl/commands/mcp.rb +194 -0
- data/lib/rubik/repl/commands/plugins.rb +208 -0
- data/lib/rubik/repl/commands/pruner.rb +71 -0
- data/lib/rubik/repl/commands/session.rb +183 -0
- data/lib/rubik/repl/commands/skills.rb +111 -0
- data/lib/rubik/repl/commands/storage.rb +85 -0
- data/lib/rubik/repl/commands/subagents.rb +195 -0
- data/lib/rubik/repl/commands/system.rb +85 -0
- data/lib/rubik/repl/commands/tasks.rb +120 -0
- data/lib/rubik/repl/commands/workspace.rb +443 -0
- data/lib/rubik/repl/commands.rb +44 -0
- data/lib/rubik/repl/composition.rb +32 -0
- data/lib/rubik/repl/config_wiring.rb +142 -0
- data/lib/rubik/repl/confirmations.rb +56 -0
- data/lib/rubik/repl/hooks.rb +47 -0
- data/lib/rubik/repl/input_execution.rb +323 -0
- data/lib/rubik/repl/input_loop.rb +213 -0
- data/lib/rubik/repl/interrupt_handler.rb +217 -0
- data/lib/rubik/repl/lifecycle.rb +410 -0
- data/lib/rubik/repl/paste_support.rb +88 -0
- data/lib/rubik/repl/pending_notice_queue.rb +60 -0
- data/lib/rubik/repl/presentation.rb +132 -0
- data/lib/rubik/repl/pruner/brief.rb +101 -0
- data/lib/rubik/repl/pruner/gate.rb +279 -0
- data/lib/rubik/repl/pruner/harness.rb +236 -0
- data/lib/rubik/repl/pruner/rules.rb +316 -0
- data/lib/rubik/repl/pruner/service.rb +82 -0
- data/lib/rubik/repl/pruner.rb +39 -0
- data/lib/rubik/repl/service_catalog.rb +374 -0
- data/lib/rubik/repl/setup_factories.rb +66 -0
- data/lib/rubik/repl/shell.rb +231 -0
- data/lib/rubik/repl/step_formatter.rb +324 -0
- data/lib/rubik/repl/tee_io.rb +105 -0
- data/lib/rubik/repl/terminal_guard.rb +157 -0
- data/lib/rubik/repl/turn_recorder.rb +234 -0
- data/lib/rubik/repl.rb +14 -0
- data/lib/rubik/runtime/ast_service.rb +78 -0
- data/lib/rubik/runtime/context.rb +335 -0
- data/lib/rubik/runtime/diagnostic_engine.rb +188 -0
- data/lib/rubik/runtime/discovery.rb +342 -0
- data/lib/rubik/runtime/fiber.rb +142 -0
- data/lib/rubik/runtime/fiber_ordering.rb +52 -0
- data/lib/rubik/runtime/fiber_retry_gate.rb +84 -0
- data/lib/rubik/runtime/index_service.rb +58 -0
- data/lib/rubik/runtime/loop_service.rb +73 -0
- data/lib/rubik/runtime/manager.rb +241 -0
- data/lib/rubik/runtime/overlay_storage.rb +481 -0
- data/lib/rubik/runtime/service_definition.rb +54 -0
- data/lib/rubik/runtime/service_registration.rb +33 -0
- data/lib/rubik/runtime/task_manager.rb +302 -0
- data/lib/rubik/runtime.rb +19 -0
- data/lib/rubik/sandbox/driver/base.rb +30 -0
- data/lib/rubik/sandbox/driver/bubblewrap.rb +111 -0
- data/lib/rubik/sandbox/driver/docker.rb +113 -0
- data/lib/rubik/sandbox/driver/podman.rb +98 -0
- data/lib/rubik/sandbox/driver/process.rb +190 -0
- data/lib/rubik/sandbox/driver.rb +91 -0
- data/lib/rubik/sandbox/runner.rb +218 -0
- data/lib/rubik/sandbox/shell_resolver.rb +178 -0
- data/lib/rubik/sandbox.rb +8 -0
- data/lib/rubik/security/blocked_guidance.rb +187 -0
- data/lib/rubik/security/classifier/classifier.rb +440 -0
- data/lib/rubik/security/classifier/commands/agent-browser.yaml +9 -0
- data/lib/rubik/security/classifier/commands/apt.yaml +25 -0
- data/lib/rubik/security/classifier/commands/awk.yaml +10 -0
- data/lib/rubik/security/classifier/commands/basename.yaml +7 -0
- data/lib/rubik/security/classifier/commands/bash-classify.yaml +8 -0
- data/lib/rubik/security/classifier/commands/bash.yaml +20 -0
- data/lib/rubik/security/classifier/commands/black.yaml +12 -0
- data/lib/rubik/security/classifier/commands/brew.yaml +31 -0
- data/lib/rubik/security/classifier/commands/builtin.yaml +8 -0
- data/lib/rubik/security/classifier/commands/cargo.yaml +114 -0
- data/lib/rubik/security/classifier/commands/cat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/chgrp.yaml +10 -0
- data/lib/rubik/security/classifier/commands/chmod.yaml +10 -0
- data/lib/rubik/security/classifier/commands/chown.yaml +10 -0
- data/lib/rubik/security/classifier/commands/column.yaml +7 -0
- data/lib/rubik/security/classifier/commands/comm.yaml +7 -0
- data/lib/rubik/security/classifier/commands/command.yaml +12 -0
- data/lib/rubik/security/classifier/commands/cp.yaml +7 -0
- data/lib/rubik/security/classifier/commands/crystal.yaml +63 -0
- data/lib/rubik/security/classifier/commands/curl.yaml +30 -0
- data/lib/rubik/security/classifier/commands/cut.yaml +7 -0
- data/lib/rubik/security/classifier/commands/date.yaml +10 -0
- data/lib/rubik/security/classifier/commands/df.yaml +7 -0
- data/lib/rubik/security/classifier/commands/diff.yaml +7 -0
- data/lib/rubik/security/classifier/commands/dig.yaml +7 -0
- data/lib/rubik/security/classifier/commands/dirname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/docker.yaml +177 -0
- data/lib/rubik/security/classifier/commands/du.yaml +7 -0
- data/lib/rubik/security/classifier/commands/echo.yaml +7 -0
- data/lib/rubik/security/classifier/commands/env.yaml +15 -0
- data/lib/rubik/security/classifier/commands/eslint.yaml +12 -0
- data/lib/rubik/security/classifier/commands/eval.yaml +8 -0
- data/lib/rubik/security/classifier/commands/exec.yaml +12 -0
- data/lib/rubik/security/classifier/commands/expand.yaml +7 -0
- data/lib/rubik/security/classifier/commands/false.yaml +7 -0
- data/lib/rubik/security/classifier/commands/file.yaml +7 -0
- data/lib/rubik/security/classifier/commands/find.yaml +25 -0
- data/lib/rubik/security/classifier/commands/fmt.yaml +7 -0
- data/lib/rubik/security/classifier/commands/fold.yaml +7 -0
- data/lib/rubik/security/classifier/commands/free.yaml +7 -0
- data/lib/rubik/security/classifier/commands/gawk.yaml +10 -0
- data/lib/rubik/security/classifier/commands/gcloud.yaml +979 -0
- data/lib/rubik/security/classifier/commands/gh.yaml +615 -0
- data/lib/rubik/security/classifier/commands/git.yaml +524 -0
- data/lib/rubik/security/classifier/commands/glab-discussion.yaml +21 -0
- data/lib/rubik/security/classifier/commands/glab.yaml +807 -0
- data/lib/rubik/security/classifier/commands/go.yaml +44 -0
- data/lib/rubik/security/classifier/commands/gog.yaml +983 -0
- data/lib/rubik/security/classifier/commands/gradle.yaml +92 -0
- data/lib/rubik/security/classifier/commands/gradlew.yaml +92 -0
- data/lib/rubik/security/classifier/commands/grep.yaml +7 -0
- data/lib/rubik/security/classifier/commands/groups.yaml +7 -0
- data/lib/rubik/security/classifier/commands/gunzip.yaml +8 -0
- data/lib/rubik/security/classifier/commands/gws.yaml +211 -0
- data/lib/rubik/security/classifier/commands/gzip.yaml +7 -0
- data/lib/rubik/security/classifier/commands/hadolint.yaml +9 -0
- data/lib/rubik/security/classifier/commands/halt.yaml +8 -0
- data/lib/rubik/security/classifier/commands/head.yaml +7 -0
- data/lib/rubik/security/classifier/commands/helm.yaml +129 -0
- data/lib/rubik/security/classifier/commands/hostname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/id.yaml +7 -0
- data/lib/rubik/security/classifier/commands/install.yaml +7 -0
- data/lib/rubik/security/classifier/commands/isort.yaml +14 -0
- data/lib/rubik/security/classifier/commands/java.yaml +35 -0
- data/lib/rubik/security/classifier/commands/javac.yaml +39 -0
- data/lib/rubik/security/classifier/commands/join.yaml +7 -0
- data/lib/rubik/security/classifier/commands/journalctl.yaml +14 -0
- data/lib/rubik/security/classifier/commands/jq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/kill.yaml +7 -0
- data/lib/rubik/security/classifier/commands/killall.yaml +7 -0
- data/lib/rubik/security/classifier/commands/kubectl.yaml +211 -0
- data/lib/rubik/security/classifier/commands/less.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ln.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ls.yaml +7 -0
- data/lib/rubik/security/classifier/commands/make.yaml +21 -0
- data/lib/rubik/security/classifier/commands/md5sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mise.yaml +579 -0
- data/lib/rubik/security/classifier/commands/mkdir.yaml +8 -0
- data/lib/rubik/security/classifier/commands/more.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mv.yaml +7 -0
- data/lib/rubik/security/classifier/commands/mvn.yaml +83 -0
- data/lib/rubik/security/classifier/commands/mvnw.yaml +83 -0
- data/lib/rubik/security/classifier/commands/mypy.yaml +9 -0
- data/lib/rubik/security/classifier/commands/netstat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/nice.yaml +11 -0
- data/lib/rubik/security/classifier/commands/nl.yaml +7 -0
- data/lib/rubik/security/classifier/commands/node.yaml +20 -0
- data/lib/rubik/security/classifier/commands/nohup.yaml +9 -0
- data/lib/rubik/security/classifier/commands/npm.yaml +71 -0
- data/lib/rubik/security/classifier/commands/npx.yaml +42 -0
- data/lib/rubik/security/classifier/commands/nslookup.yaml +7 -0
- data/lib/rubik/security/classifier/commands/paste.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ping.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pip.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pip3.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pipx.yaml +33 -0
- data/lib/rubik/security/classifier/commands/pkill.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pnpm.yaml +346 -0
- data/lib/rubik/security/classifier/commands/poetry.yaml +97 -0
- data/lib/rubik/security/classifier/commands/poweroff.yaml +8 -0
- data/lib/rubik/security/classifier/commands/prettier.yaml +13 -0
- data/lib/rubik/security/classifier/commands/printenv.yaml +7 -0
- data/lib/rubik/security/classifier/commands/printf.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ps.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pup.yaml +882 -0
- data/lib/rubik/security/classifier/commands/pwd.yaml +7 -0
- data/lib/rubik/security/classifier/commands/pytest.yaml +7 -0
- data/lib/rubik/security/classifier/commands/python.yaml +9 -0
- data/lib/rubik/security/classifier/commands/python3.yaml +9 -0
- data/lib/rubik/security/classifier/commands/read.yaml +7 -0
- data/lib/rubik/security/classifier/commands/readlink.yaml +7 -0
- data/lib/rubik/security/classifier/commands/realpath.yaml +7 -0
- data/lib/rubik/security/classifier/commands/reboot.yaml +8 -0
- data/lib/rubik/security/classifier/commands/rev.yaml +7 -0
- data/lib/rubik/security/classifier/commands/rg.yaml +7 -0
- data/lib/rubik/security/classifier/commands/rm.yaml +8 -0
- data/lib/rubik/security/classifier/commands/rsync.yaml +12 -0
- data/lib/rubik/security/classifier/commands/rtk.yaml +140 -0
- data/lib/rubik/security/classifier/commands/ruff.yaml +29 -0
- data/lib/rubik/security/classifier/commands/rustfmt.yaml +14 -0
- data/lib/rubik/security/classifier/commands/scp.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sed.yaml +14 -0
- data/lib/rubik/security/classifier/commands/seq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sh.yaml +14 -0
- data/lib/rubik/security/classifier/commands/sha1sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/sha256sum.yaml +7 -0
- data/lib/rubik/security/classifier/commands/shellcheck.yaml +10 -0
- data/lib/rubik/security/classifier/commands/shutdown.yaml +8 -0
- data/lib/rubik/security/classifier/commands/slack.yaml +113 -0
- data/lib/rubik/security/classifier/commands/sleep.yaml +8 -0
- data/lib/rubik/security/classifier/commands/snip.yaml +79 -0
- data/lib/rubik/security/classifier/commands/sort.yaml +10 -0
- data/lib/rubik/security/classifier/commands/ss.yaml +7 -0
- data/lib/rubik/security/classifier/commands/ssh.yaml +41 -0
- data/lib/rubik/security/classifier/commands/stat.yaml +7 -0
- data/lib/rubik/security/classifier/commands/stdbuf.yaml +15 -0
- data/lib/rubik/security/classifier/commands/strace.yaml +20 -0
- data/lib/rubik/security/classifier/commands/strings.yaml +11 -0
- data/lib/rubik/security/classifier/commands/sudo.yaml +24 -0
- data/lib/rubik/security/classifier/commands/systemctl.yaml +58 -0
- data/lib/rubik/security/classifier/commands/systemd-run.yaml +30 -0
- data/lib/rubik/security/classifier/commands/tac.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tail.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tar.yaml +25 -0
- data/lib/rubik/security/classifier/commands/tee.yaml +8 -0
- data/lib/rubik/security/classifier/commands/terraform.yaml +70 -0
- data/lib/rubik/security/classifier/commands/test.yaml +7 -0
- data/lib/rubik/security/classifier/commands/time.yaml +8 -0
- data/lib/rubik/security/classifier/commands/timeout.yaml +18 -0
- data/lib/rubik/security/classifier/commands/top.yaml +7 -0
- data/lib/rubik/security/classifier/commands/touch.yaml +8 -0
- data/lib/rubik/security/classifier/commands/tr.yaml +7 -0
- data/lib/rubik/security/classifier/commands/traceroute.yaml +7 -0
- data/lib/rubik/security/classifier/commands/true.yaml +7 -0
- data/lib/rubik/security/classifier/commands/tsc.yaml +16 -0
- data/lib/rubik/security/classifier/commands/type.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uname.yaml +7 -0
- data/lib/rubik/security/classifier/commands/unexpand.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uniq.yaml +7 -0
- data/lib/rubik/security/classifier/commands/unzip.yaml +10 -0
- data/lib/rubik/security/classifier/commands/uptime.yaml +7 -0
- data/lib/rubik/security/classifier/commands/uv.yaml +156 -0
- data/lib/rubik/security/classifier/commands/wait.yaml +11 -0
- data/lib/rubik/security/classifier/commands/wc.yaml +7 -0
- data/lib/rubik/security/classifier/commands/wget.yaml +7 -0
- data/lib/rubik/security/classifier/commands/whereis.yaml +7 -0
- data/lib/rubik/security/classifier/commands/which.yaml +7 -0
- data/lib/rubik/security/classifier/commands/whoami.yaml +7 -0
- data/lib/rubik/security/classifier/commands/xargs.yaml +26 -0
- data/lib/rubik/security/classifier/commands/xmllint.yaml +7 -0
- data/lib/rubik/security/classifier/commands/yarn.yaml +72 -0
- data/lib/rubik/security/classifier/commands/yes.yaml +7 -0
- data/lib/rubik/security/classifier/commands/yq.yaml +10 -0
- data/lib/rubik/security/classifier/commands/zip.yaml +8 -0
- data/lib/rubik/security/classifier/commands/zsh.yaml +14 -0
- data/lib/rubik/security/classifier/database.rb +216 -0
- data/lib/rubik/security/classifier/matcher/delegation.rb +190 -0
- data/lib/rubik/security/classifier/matcher/options.rb +245 -0
- data/lib/rubik/security/classifier/matcher/subcommands.rb +103 -0
- data/lib/rubik/security/classifier/matcher.rb +249 -0
- data/lib/rubik/security/classifier/models.rb +170 -0
- data/lib/rubik/security/classifier/parser.rb +23 -0
- data/lib/rubik/security/classifier/parsers/bash.rb +280 -0
- data/lib/rubik/security/classifier/parsers/bash_extractors.rb +221 -0
- data/lib/rubik/security/classifier/parsers/powershell.rb +185 -0
- data/lib/rubik/security/classifier/pwsh_rules.rb +224 -0
- data/lib/rubik/security/classifier.rb +25 -0
- data/lib/rubik/security/errors.rb +39 -0
- data/lib/rubik/security/guard.rb +954 -0
- data/lib/rubik/security/redactor.rb +89 -0
- data/lib/rubik/security.rb +11 -0
- data/lib/rubik/session/manager.rb +331 -0
- data/lib/rubik/session/recorder.rb +149 -0
- data/lib/rubik/session/replayer.rb +65 -0
- data/lib/rubik/session.rb +10 -0
- data/lib/rubik/skills/frontmatter.rb +41 -0
- data/lib/rubik/skills/manager.rb +223 -0
- data/lib/rubik/skills/registry.rb +64 -0
- data/lib/rubik/skills/skill.rb +33 -0
- data/lib/rubik/skills/storage.rb +77 -0
- data/lib/rubik/skills/validator.rb +116 -0
- data/lib/rubik/skills.rb +20 -0
- data/lib/rubik/storage/folding.rb +23 -0
- data/lib/rubik/storage/layout.rb +145 -0
- data/lib/rubik/storage/render.rb +97 -0
- data/lib/rubik/storage/service.rb +42 -0
- data/lib/rubik/storage/store.rb +514 -0
- data/lib/rubik/storage/transcript_indexer.rb +116 -0
- data/lib/rubik/storage/uri_reader.rb +168 -0
- data/lib/rubik/storage/vakanuvis/annal_writer.rb +92 -0
- data/lib/rubik/storage/vakanuvis/distill_gate.rb +323 -0
- data/lib/rubik/storage/vakanuvis/gate.rb +250 -0
- data/lib/rubik/storage/vakanuvis/harness.rb +421 -0
- data/lib/rubik/storage/vakanuvis/memory_steward.rb +163 -0
- data/lib/rubik/storage/vakanuvis/service.rb +69 -0
- data/lib/rubik/storage/vakanuvis/vocabulary.rb +99 -0
- data/lib/rubik/storage/vakanuvis/wiki_skeleton.rb +67 -0
- data/lib/rubik/storage/vakanuvis.rb +77 -0
- data/lib/rubik/storage.rb +29 -0
- data/lib/rubik/tools/base.rb +107 -0
- data/lib/rubik/tools/builtins/dmesg.rb +74 -0
- data/lib/rubik/tools/builtins/exec.rb +293 -0
- data/lib/rubik/tools/builtins/files.rb +354 -0
- data/lib/rubik/tools/builtins/goal_ledger.rb +89 -0
- data/lib/rubik/tools/builtins/grep.rb +281 -0
- data/lib/rubik/tools/builtins/interactive.rb +175 -0
- data/lib/rubik/tools/builtins/lifecycle.rb +359 -0
- data/lib/rubik/tools/builtins/lsp.rb +283 -0
- data/lib/rubik/tools/builtins/pruner.rb +88 -0
- data/lib/rubik/tools/builtins/runtime.rb +120 -0
- data/lib/rubik/tools/builtins/send_message.rb +109 -0
- data/lib/rubik/tools/builtins/show_file.rb +84 -0
- data/lib/rubik/tools/builtins/skill.rb +72 -0
- data/lib/rubik/tools/builtins/storage.rb +232 -0
- data/lib/rubik/tools/builtins/subagent.rb +356 -0
- data/lib/rubik/tools/builtins/subagent_lifecycle.rb +72 -0
- data/lib/rubik/tools/builtins/symbols.rb +209 -0
- data/lib/rubik/tools/builtins/synthesis.rb +314 -0
- data/lib/rubik/tools/builtins/task.rb +168 -0
- data/lib/rubik/tools/builtins/unsynthesize.rb +111 -0
- data/lib/rubik/tools/builtins/vakanuvis.rb +161 -0
- data/lib/rubik/tools/builtins.rb +30 -0
- data/lib/rubik/tools/catalog.rb +70 -0
- data/lib/rubik/tools/declaration.rb +90 -0
- data/lib/rubik/tools/diagnostic_reporter.rb +283 -0
- data/lib/rubik/tools/fuzzy_matcher.rb +172 -0
- data/lib/rubik/tools/native_search.rb +180 -0
- data/lib/rubik/tools/output.rb +54 -0
- data/lib/rubik/tools/preflight_runner.rb +209 -0
- data/lib/rubik/tools/registry.rb +247 -0
- data/lib/rubik/tools/result.rb +71 -0
- data/lib/rubik/tools/services.rb +96 -0
- data/lib/rubik/tools/truncator.rb +78 -0
- data/lib/rubik/tools/uri_resolver.rb +195 -0
- data/lib/rubik/tools.rb +14 -0
- data/lib/rubik/ui/approvals.rb +52 -0
- data/lib/rubik/ui/banner.rb +43 -0
- data/lib/rubik/ui/capability.rb +122 -0
- data/lib/rubik/ui/dialog.rb +106 -0
- data/lib/rubik/ui/diff_viewer.rb +78 -0
- data/lib/rubik/ui/frame.rb +95 -0
- data/lib/rubik/ui/markdown_stream.rb +107 -0
- data/lib/rubik/ui/panel.rb +98 -0
- data/lib/rubik/ui/renderer.rb +206 -0
- data/lib/rubik/ui/spinner.rb +120 -0
- data/lib/rubik/ui/statusline.rb +134 -0
- data/lib/rubik/ui/theme.rb +118 -0
- data/lib/rubik/ui.rb +57 -0
- data/lib/rubik/uris.rb +101 -0
- data/lib/rubik/version.rb +5 -0
- data/lib/rubik.rb +65 -0
- metadata +770 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "commands/base"
|
|
4
|
+
require_relative "commands/system"
|
|
5
|
+
require_relative "commands/buffer"
|
|
6
|
+
require_relative "commands/session"
|
|
7
|
+
require_relative "commands/workspace"
|
|
8
|
+
require_relative "commands/lsp"
|
|
9
|
+
require_relative "commands/agent"
|
|
10
|
+
require_relative "commands/plugins"
|
|
11
|
+
require_relative "commands/mcp"
|
|
12
|
+
require_relative "commands/skills"
|
|
13
|
+
require_relative "commands/tasks"
|
|
14
|
+
require_relative "commands/subagents"
|
|
15
|
+
require_relative "commands/storage"
|
|
16
|
+
require_relative "commands/pruner"
|
|
17
|
+
require_relative "commands/dmesg"
|
|
18
|
+
|
|
19
|
+
module Rubik
|
|
20
|
+
module REPL
|
|
21
|
+
module Commands
|
|
22
|
+
MODULES = [
|
|
23
|
+
System,
|
|
24
|
+
Buffer,
|
|
25
|
+
Session,
|
|
26
|
+
Workspace,
|
|
27
|
+
Lsp,
|
|
28
|
+
Agent,
|
|
29
|
+
Plugins,
|
|
30
|
+
Mcp,
|
|
31
|
+
Skills,
|
|
32
|
+
Tasks,
|
|
33
|
+
Subagents,
|
|
34
|
+
Storage,
|
|
35
|
+
Pruner,
|
|
36
|
+
Dmesg
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
def self.register_all(shell)
|
|
40
|
+
MODULES.each { |mod| mod.register(shell) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "service_catalog"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module REPL
|
|
7
|
+
# Plan 63 Faz B: pure construction layer extracted from Shell#initialize.
|
|
8
|
+
# Candidate 05: WHAT gets built, in which order, and with which wiring
|
|
9
|
+
# facts lives in ServiceCatalog (the single declaration point); this
|
|
10
|
+
# module only resolves the effective root, seeds the override scope,
|
|
11
|
+
# folds the catalog and packs the Parts struct. Every part stays
|
|
12
|
+
# injectable for tests through the Shell's optional parts: kwarg (and
|
|
13
|
+
# through this module's config/event_bus/buffer_manager/snapshot_store
|
|
14
|
+
# override kwargs, which adopt their catalog entry verbatim).
|
|
15
|
+
module Composition
|
|
16
|
+
Parts = ServiceCatalog.parts_struct
|
|
17
|
+
|
|
18
|
+
def self.build(config:, root_dir: nil, event_bus: nil, buffer_manager: nil, snapshot_store: nil, dmesg: nil, dispatcher: nil, init: nil)
|
|
19
|
+
effective_root = File.expand_path(root_dir || buffer_manager&.root_dir || config&.root_dir || Dir.pwd)
|
|
20
|
+
scope = ServiceCatalog::Scope.new(
|
|
21
|
+
root_dir: effective_root,
|
|
22
|
+
overrides: {
|
|
23
|
+
config: config, event_bus: event_bus,
|
|
24
|
+
buffer_manager: buffer_manager, snapshot_store: snapshot_store,
|
|
25
|
+
dmesg: dmesg, dispatcher: dispatcher, init: init
|
|
26
|
+
}.compact
|
|
27
|
+
)
|
|
28
|
+
Parts.new(**ServiceCatalog.build(scope))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module REPL
|
|
5
|
+
# Wires reactive configuration observers to Shell subsystems.
|
|
6
|
+
# Returns an array of cleanup procs (inverses) that can be unrolled on shutdown.
|
|
7
|
+
module ConfigWiring
|
|
8
|
+
class << self
|
|
9
|
+
def wire(shell)
|
|
10
|
+
config = shell.config
|
|
11
|
+
return [] unless config.respond_to?(:on_change)
|
|
12
|
+
|
|
13
|
+
cleanups = []
|
|
14
|
+
|
|
15
|
+
cleanups << config.on_change("workspace") do |key, val, _old|
|
|
16
|
+
wire_workspace_change(shell, key, val)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
cleanups << config.on_change("context") do |key, val, _old|
|
|
20
|
+
wire_context_change(shell, key, val)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
cleanups << config.on_change("lsp") do |key, val, _old|
|
|
24
|
+
wire_lsp_change(shell, key, val)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
["llm", "models", "model", "providers", "router"].each do |prefix|
|
|
28
|
+
cleanups << config.on_change(prefix) do |_key, _val, _old|
|
|
29
|
+
wire_llm_change(shell)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
cleanups << config.on_change("ui") do |_key, _val, _old|
|
|
34
|
+
shell.refresh_ui! if shell.respond_to?(:refresh_ui!)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
cleanups << config.on_change("skills") do |_key, _val, _old|
|
|
38
|
+
shell.skill_manager&.apply_config!(tool_registry: shell.tool_registry)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
cleanups << config.on_change("security.command_allowlist") do |_key, val, _old|
|
|
42
|
+
wire_security_change(shell, val)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
cleanups << config.on_change("security.allow_safe_probes") do |_key, val, _old|
|
|
46
|
+
next unless shell.guard.respond_to?(:allow_safe_probes=)
|
|
47
|
+
|
|
48
|
+
shell.guard.allow_safe_probes = ([true, "true"].include?(val))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Audit destination is live-reconfigurable too (nil/blank disables,
|
|
52
|
+
# Guard#audit_log_path= normalizes) — otherwise a mid-session
|
|
53
|
+
# !config set security.audit_log_path would confirm silently while
|
|
54
|
+
# events kept flowing to the old path.
|
|
55
|
+
cleanups << config.on_change("security.audit_log_path") do |_key, val, _old|
|
|
56
|
+
shell.guard.audit_log_path = val if shell.guard.respond_to?(:audit_log_path=)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
cleanups << config.on_change("network.allow") do |_key, val, _old|
|
|
60
|
+
config.set("sandbox.allow_network", val, persist: false) if config.fetch("sandbox.allow_network", nil) != val
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
cleanups << config.on_change("sandbox.allow_network") do |_key, val, _old|
|
|
64
|
+
config.set("network.allow", val, persist: false) if config.fetch("network.allow", nil) != val
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
cleanups
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def wire_workspace_change(shell, key, typed_val)
|
|
73
|
+
case key
|
|
74
|
+
when "workspace.additional_roots"
|
|
75
|
+
return unless typed_val.is_a?(Array)
|
|
76
|
+
|
|
77
|
+
exp_roots = typed_val.map { |r| File.expand_path(r.to_s, shell.buffer_manager&.root_dir || Dir.pwd) }
|
|
78
|
+
current_roots = shell.buffer_manager&.additional_roots || []
|
|
79
|
+
new_roots = exp_roots - current_roots
|
|
80
|
+
removed_roots = current_roots - exp_roots
|
|
81
|
+
|
|
82
|
+
new_roots.each { |r| shell.buffer_manager&.add_root(r) }
|
|
83
|
+
removed_roots.each { |r| shell.buffer_manager&.remove_root(r) }
|
|
84
|
+
|
|
85
|
+
if (new_roots.any? || removed_roots.any?) &&
|
|
86
|
+
shell.config.fetch("workspace.auto_index_additional", true) && shell.context_index
|
|
87
|
+
shell.context_index.additional_roots = shell.buffer_manager&.additional_roots
|
|
88
|
+
shell.context_index.full_scan
|
|
89
|
+
shell.warm_ast_graph(async: false) if shell.respond_to?(:warm_ast_graph)
|
|
90
|
+
end
|
|
91
|
+
when "workspace.allowed_external_dirs"
|
|
92
|
+
return unless typed_val.is_a?(Array)
|
|
93
|
+
|
|
94
|
+
exp_dirs = typed_val.map { |d| File.expand_path(d.to_s, shell.buffer_manager&.root_dir || Dir.pwd) }
|
|
95
|
+
current_dirs = shell.buffer_manager&.allowed_external_dirs || []
|
|
96
|
+
new_dirs = exp_dirs - current_dirs
|
|
97
|
+
removed_dirs = current_dirs - exp_dirs
|
|
98
|
+
|
|
99
|
+
new_dirs.each do |d|
|
|
100
|
+
shell.buffer_manager.add_allowed_dir(d) if shell.buffer_manager.respond_to?(:add_allowed_dir)
|
|
101
|
+
shell.sandbox_runner&.add_allowed_dir(d)
|
|
102
|
+
end
|
|
103
|
+
removed_dirs.each do |d|
|
|
104
|
+
shell.buffer_manager.remove_allowed_dir(d) if shell.buffer_manager.respond_to?(:remove_allowed_dir)
|
|
105
|
+
shell.sandbox_runner&.remove_allowed_dir(d)
|
|
106
|
+
end
|
|
107
|
+
when "workspace.allow_external_writes"
|
|
108
|
+
shell.buffer_manager.allow_external_writes = typed_val if shell.buffer_manager.respond_to?(:allow_external_writes=)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def wire_context_change(shell, key, _typed_val)
|
|
113
|
+
return unless ["context.include", "context.exclude"].include?(key)
|
|
114
|
+
return unless shell.context_index.respond_to?(:update_file_filters)
|
|
115
|
+
|
|
116
|
+
inc = shell.config.fetch("context.include", [])
|
|
117
|
+
exc = shell.config.fetch("context.exclude", [])
|
|
118
|
+
shell.context_index.update_file_filters(includes: inc, excludes: exc)
|
|
119
|
+
shell.warm_ast_graph(async: false) if shell.respond_to?(:warm_ast_graph)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def wire_lsp_change(shell, key, typed_val)
|
|
123
|
+
shell.lsp_manager&.stop_all if key == "lsp.enabled" && typed_val == false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Reactive command-allowlist wiring (Plan: komut-allowlist): config is
|
|
127
|
+
# the single source of truth; the live Guard is reconfigured in place —
|
|
128
|
+
# zero restart (Plan 75 pattern).
|
|
129
|
+
def wire_security_change(shell, typed_val)
|
|
130
|
+
return unless typed_val.is_a?(Array)
|
|
131
|
+
return unless shell.guard.respond_to?(:set_allowed_commands)
|
|
132
|
+
|
|
133
|
+
shell.guard.set_allowed_commands(typed_val)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def wire_llm_change(shell)
|
|
137
|
+
shell.refresh_llm_registry! if shell.respond_to?(:refresh_llm_registry!)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module REPL
|
|
5
|
+
# Interactive confirmation helpers mixed into Shell so every y/n prompt
|
|
6
|
+
# (max-steps continue, !plan approval) routes through Reline with one
|
|
7
|
+
# consistent notion of "interactive". Mixing raw #gets with Reline wedges
|
|
8
|
+
# the prompt on Windows, so all confirmations live here.
|
|
9
|
+
module Confirmations
|
|
10
|
+
# Asks a yes/no question. Non-interactive contexts (tests, piped IO,
|
|
11
|
+
# test-capture tees) short-circuit to the default answer WITHOUT
|
|
12
|
+
# reading input. Returns the downcased answer String ("y"/"n"/...) so
|
|
13
|
+
# call sites keep their %w[y yes evet] handling. Ctrl+C, EOF, or a bare
|
|
14
|
+
# Enter all select the default answer instead of unwinding the loop.
|
|
15
|
+
def ask_yes_no(prompt, default: "n")
|
|
16
|
+
default_answer = default.to_s.downcase
|
|
17
|
+
return default_answer unless interactive_input?
|
|
18
|
+
|
|
19
|
+
# Fast single-key approval on color-capable TTYs (UI::Approvals);
|
|
20
|
+
# plain terminals keep the classic Reline line prompt.
|
|
21
|
+
return Rubik::UI::Approvals.keypress_yes_no(self, prompt, default: default_answer, theme: @ui_theme) if @ui_theme&.color?
|
|
22
|
+
|
|
23
|
+
hint = default_answer.start_with?("y") ? "[Y/n]" : "[y/N]"
|
|
24
|
+
answer = @reline.readline("#{prompt} #{hint}: ", false)
|
|
25
|
+
answer = default_answer if answer.nil? # EOF (Ctrl+D/Ctrl+Z) -> default
|
|
26
|
+
answer = answer.to_s.strip.downcase
|
|
27
|
+
answer.empty? ? default_answer : answer
|
|
28
|
+
rescue Interrupt
|
|
29
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
30
|
+
@output.puts "^C"
|
|
31
|
+
default_answer
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# True only when the Shell is attached to a live interactive console.
|
|
35
|
+
# A TeeIO wrapping REAL output (production command capture: its StringIO
|
|
36
|
+
# target makes test_capture? true) still counts as interactive; only
|
|
37
|
+
# tees whose every target is a StringIO (test harness) do not.
|
|
38
|
+
def interactive_input?
|
|
39
|
+
return false if ENV["RUBIK_TEST"] == "1"
|
|
40
|
+
|
|
41
|
+
out = @output
|
|
42
|
+
if out.is_a?(TeeIO)
|
|
43
|
+
real_targets = out.targets.compact.grep_v(StringIO)
|
|
44
|
+
return false if real_targets.empty?
|
|
45
|
+
|
|
46
|
+
out = real_targets.first
|
|
47
|
+
end
|
|
48
|
+
return false if out.is_a?(StringIO)
|
|
49
|
+
return false if out.respond_to?(:test_capture?) && out.test_capture?
|
|
50
|
+
|
|
51
|
+
input = @input || $stdin
|
|
52
|
+
input.respond_to?(:tty?) && input.tty?
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module REPL
|
|
7
|
+
class Hooks
|
|
8
|
+
KNOWN_EVENTS = [:file_changed, :test_failed, :before_command, :after_command].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(logger: Logger.new($stderr))
|
|
11
|
+
@hooks = Hash.new { |h, k| h[k] = [] }
|
|
12
|
+
@logger = logger
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(event_name, &block)
|
|
17
|
+
return unless block_given?
|
|
18
|
+
|
|
19
|
+
event_key = event_name.to_sym
|
|
20
|
+
@mutex.synchronize do
|
|
21
|
+
@hooks[event_key] << block
|
|
22
|
+
end
|
|
23
|
+
block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def unregister(event_name, token)
|
|
27
|
+
return unless token
|
|
28
|
+
|
|
29
|
+
event_key = event_name.to_sym
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
@hooks[event_key].delete(token)
|
|
32
|
+
end
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def trigger(event_name, *)
|
|
37
|
+
event_key = event_name.to_sym
|
|
38
|
+
listeners = @mutex.synchronize { @hooks[event_key].dup }
|
|
39
|
+
listeners.each do |hook|
|
|
40
|
+
hook.call(*)
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
@logger.error("Hook error on event '#{event_name}': #{e.message}")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require_relative "tee_io"
|
|
5
|
+
|
|
6
|
+
module Rubik
|
|
7
|
+
module REPL
|
|
8
|
+
# Öneri 3 (Kademe 2): Shell'den ayrılan girdi yürütme ve çıktı yakalama
|
|
9
|
+
# sorumluluğu. with_output/capture_output I/O geçişini; execute_command
|
|
10
|
+
# bang-komut dispatch'ini (kayıt, truncation, konuşma geçmişi işleme);
|
|
11
|
+
# execute_free_text serbest metin turn'inin tam yaşam döngüsünü kodlar
|
|
12
|
+
# (route → loop kurulum → devam sorusu → araç-geçmişi birleştirme →
|
|
13
|
+
# kayıt → akış/render → hata/iptal temizliği).
|
|
14
|
+
# Durum Shell'de yaşar (@output, @recorder, @conversation_history,
|
|
15
|
+
# @active_loop, ...); bu modül yalnızca yürütme politikasını kodlar.
|
|
16
|
+
module InputExecution
|
|
17
|
+
def with_output(new_io)
|
|
18
|
+
orig = @output
|
|
19
|
+
@output = new_io
|
|
20
|
+
@terminal_guard.output = new_io if @terminal_guard
|
|
21
|
+
yield
|
|
22
|
+
ensure
|
|
23
|
+
@output = orig
|
|
24
|
+
@terminal_guard.output = orig if @terminal_guard
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def capture_output(&)
|
|
28
|
+
StringIO.new.tap { |io| with_output(io, &) }.string
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Per-dispatch closed view handed to bang-command handlers (see
|
|
32
|
+
# CommandContext). Cheap by design — holds only the shell reference;
|
|
33
|
+
# the capture-aware IO is read live off the shell, so it reflects
|
|
34
|
+
# with_output's swap at call time.
|
|
35
|
+
def command_context
|
|
36
|
+
Rubik::REPL::CommandContext.new(self)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def execute_command(parsed)
|
|
40
|
+
# Plan 109 staleness rule: this turn's context already includes the
|
|
41
|
+
# idle-injected notices in @conversation_history — a surviving
|
|
42
|
+
# pending wake would turn the next stray Enter into a redundant
|
|
43
|
+
# notice turn.
|
|
44
|
+
clear_pending_wake_notices! if respond_to?(:clear_pending_wake_notices!)
|
|
45
|
+
cmd_name = @plugin_manager ? @plugin_manager.resolve_alias(parsed.command_name) : parsed.command_name.to_s.downcase.delete_prefix("!")
|
|
46
|
+
cmd_meta = @commands[cmd_name]
|
|
47
|
+
|
|
48
|
+
if cmd_meta
|
|
49
|
+
@hooks.trigger(:before_command, parsed.command_name, parsed.args)
|
|
50
|
+
|
|
51
|
+
# Lifecycle cmds + goal (which self-manages its own history turns)
|
|
52
|
+
untracked_commands = ["quit", "exit", "clear", "reset", "goal"]
|
|
53
|
+
if untracked_commands.include?(parsed.command_name.downcase)
|
|
54
|
+
begin
|
|
55
|
+
cmd_meta[:handler].call(parsed.args, command_context)
|
|
56
|
+
rescue Interrupt
|
|
57
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
58
|
+
@output.puts "\n⚠️ [Rubik] Komut kullanıcı tarafından iptal edildi (^C)."
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
captured = StringIO.new
|
|
62
|
+
cmd_interrupted = false
|
|
63
|
+
with_output(TeeIO.new(@output, captured)) do
|
|
64
|
+
# Built INSIDE the capture swap so ctx.out is the TeeIO — the
|
|
65
|
+
# handler's prints reach the recorder and LLM-history truncation.
|
|
66
|
+
cmd_meta[:handler].call(parsed.args, command_context)
|
|
67
|
+
rescue Interrupt
|
|
68
|
+
cmd_interrupted = true
|
|
69
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
70
|
+
@output.puts "\n⚠️ [Rubik] Komut kullanıcı tarafından iptal edildi (^C)."
|
|
71
|
+
ensure
|
|
72
|
+
# A command (!plan/!goal) may leave a live spinner if it died mid-wait.
|
|
73
|
+
@ui_renderer&.spinner&.kill
|
|
74
|
+
end
|
|
75
|
+
output_text = captured.string.strip
|
|
76
|
+
if !cmd_interrupted && !output_text.empty?
|
|
77
|
+
clean_raw = Rubik::Security::Redactor.redact(parsed.raw)
|
|
78
|
+
@recorder&.record_repl_command(clean_raw, output_text)
|
|
79
|
+
# Plan 61 D-8: bound what a bang command leaves in LLM memory; the recorder keeps the full text.
|
|
80
|
+
trunc = Rubik::Tools::Truncator.truncate(output_text, max_chars: @config.fetch("tools.max_output_chars", 12_000), strategy: :head_tail)
|
|
81
|
+
@conversation_history << { role: "user", content: clean_raw.delete_prefix("!") } # bangs stay out of LLM history; recorder keeps the literal
|
|
82
|
+
@conversation_history << { role: "assistant", content: trunc[:truncated] ? trunc[:text] : output_text }
|
|
83
|
+
trim_conversation_window!
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
@hooks.trigger(:after_command, parsed.command_name, parsed.args)
|
|
88
|
+
reap_dead_kernel_units! if respond_to?(:reap_dead_kernel_units!)
|
|
89
|
+
else
|
|
90
|
+
available = @commands.keys.sort.map { |c| "!#{c}" }.join(", ")
|
|
91
|
+
@output.puts "Bilinmeyen komut: !#{parsed.command_name}. Kullanılabilir komutlar: #{available} (!help'e bakın)."
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Plan 109 Faz B4: marker prefix makes the system-initiated origin
|
|
96
|
+
# visible in the recorder/session (user decision: honest provenance).
|
|
97
|
+
NOTICE_TURN_MARKER = "[reactive subagent wake-up]"
|
|
98
|
+
|
|
99
|
+
# Plan 109 Faz B4: the autonomous notice-evaluation turn. The queued
|
|
100
|
+
# notices ALREADY ride @conversation_history as
|
|
101
|
+
# <subagent_notifications> XML (idle-branch injection); the drained
|
|
102
|
+
# payload only builds a roster so the model can target ids
|
|
103
|
+
# immediately — the prompt never duplicates the XML. Reentrancy is
|
|
104
|
+
# excluded by construction: inside execute_free_text, @active_loop
|
|
105
|
+
# is set before orchestrator.run, so any event arriving mid-turn
|
|
106
|
+
# takes the ACTIVE path (loop inbox), never the idle path — no
|
|
107
|
+
# recursive wake. Wakeloop fix 2026-10-09: the residual idle-path
|
|
108
|
+
# windows (an event landing before @active_loop is set, or in the
|
|
109
|
+
# ensure tail after it is cleared) are closed by @autonomous_turn —
|
|
110
|
+
# route_subagent_notice's idle branch still injects and prints but
|
|
111
|
+
# does not arm the wake while this flag is up, so an autonomous turn
|
|
112
|
+
# can never chain into another one.
|
|
113
|
+
def execute_subagent_notice_turn!
|
|
114
|
+
notices = drain_pending_wake_notices!
|
|
115
|
+
return false if notices.empty?
|
|
116
|
+
|
|
117
|
+
roster = notices.map { |n| "#{n[:id]}:#{n[:kind]}" }.uniq.join(", ")
|
|
118
|
+
# Wakeloop fix 2026-10-09: an AUTONOMOUS turn must not spawn another
|
|
119
|
+
# one. Stranded notices this turn leaves behind still reach history
|
|
120
|
+
# (no loss), but they do not re-arm the wake — the prompt comes back
|
|
121
|
+
# and the backlog waits for the user's next Enter.
|
|
122
|
+
begin
|
|
123
|
+
@autonomous_turn = true
|
|
124
|
+
execute_free_text(
|
|
125
|
+
"#{NOTICE_TURN_MARKER} Background subagent activity arrived while the prompt was idle " \
|
|
126
|
+
"(#{roster}). Evaluate the <subagent_notifications> blocks in your conversation history: " \
|
|
127
|
+
"extend the budget of paused-but-promising subagents (manage_subagents action: 'extend_budget'), " \
|
|
128
|
+
"steer with send_message, retrieve finished results (manage_subagents action: 'wait'), " \
|
|
129
|
+
"wrap up or kill stale work, or report outcomes to the user. The user did not type this " \
|
|
130
|
+
"turn — it fired automatically. If nothing needs your attention, finish with a one-line status summary."
|
|
131
|
+
)
|
|
132
|
+
ensure
|
|
133
|
+
@autonomous_turn = false
|
|
134
|
+
end
|
|
135
|
+
true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def execute_free_text(text)
|
|
139
|
+
# Wakeloop fix 2026-10-09: ^C settles the wake debt instead of
|
|
140
|
+
# letting the interrupted turn's own ensure re-arm the cycle. Bound
|
|
141
|
+
# FIRST: every line between here and the rescue clauses can raise
|
|
142
|
+
# (recorder file IO, bus emit), and the ensure reads this local —
|
|
143
|
+
# an unbound read would mask the in-flight exception with a
|
|
144
|
+
# NameError, breaking the "cleanup must never mask" contract.
|
|
145
|
+
interrupted = false
|
|
146
|
+
# Plan 109 staleness rule (see execute_command): a real turn's
|
|
147
|
+
# context already includes the idle-injected notices. The notice
|
|
148
|
+
# turn itself drains first, so this clear is a no-op for it.
|
|
149
|
+
clear_pending_wake_notices! if respond_to?(:clear_pending_wake_notices!)
|
|
150
|
+
@recorder&.record_user_turn(text)
|
|
151
|
+
@event_bus.emit(:prompt_received, prompt: text)
|
|
152
|
+
|
|
153
|
+
renderer = md_stream = orchestrator = nil
|
|
154
|
+
routed = resolve_turn_route(text)
|
|
155
|
+
provider = routed[:provider]
|
|
156
|
+
|
|
157
|
+
if provider.nil?
|
|
158
|
+
spec_str = routed[:spec].is_a?(Hash) ? "#{routed[:spec]['provider']}/#{routed[:spec]['name']}" : routed[:spec].to_s
|
|
159
|
+
@output.puts "Hata: Model sağlayıcısı başlatılamadı (#{spec_str})."
|
|
160
|
+
record_failed_turn!(text, "failed", "no LLM provider available (#{spec_str})", nil)
|
|
161
|
+
return
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
@runtime_manager.provide(:llm_client, provider)
|
|
165
|
+
|
|
166
|
+
# Pruner apply seam (context-pruner 2026-10-09): budayıcı bir plan
|
|
167
|
+
# aşamaladıysa pencereyi turn tohumlanmadan ÖNCE takas et (knob
|
|
168
|
+
# kapalıysa no-op; !plan'ın execute_free_text devri ve otonom notice
|
|
169
|
+
# turn'leri buradan geçer).
|
|
170
|
+
apply_staged_prune_plan!
|
|
171
|
+
|
|
172
|
+
max_steps = @config.fetch("agent.max_steps", 40).to_i
|
|
173
|
+
warn_ratio = @config.fetch("agent.warn_at_ratio", 0.75).to_f
|
|
174
|
+
theme = self.theme
|
|
175
|
+
renderer = @ui_renderer || Rubik::UI::Renderer.new(config: @config, max_steps: max_steps, output: @output, theme: theme)
|
|
176
|
+
md_enabled = @config.fetch("ui.markdown", "live") != "off"
|
|
177
|
+
md_stream = Rubik::UI::MarkdownStream.new(theme, enabled: md_enabled)
|
|
178
|
+
|
|
179
|
+
orchestrator = orchestrator_loop(
|
|
180
|
+
provider: provider,
|
|
181
|
+
model: routed[:model],
|
|
182
|
+
max_steps: max_steps,
|
|
183
|
+
warn_at_ratio: warn_ratio,
|
|
184
|
+
context_limit: routed[:context_limit],
|
|
185
|
+
max_output_tokens: routed[:max_output_tokens],
|
|
186
|
+
on_step: lambda { |step_info|
|
|
187
|
+
case step_info[:type]&.to_sym
|
|
188
|
+
when :chunk
|
|
189
|
+
renderer.render(step_info) # stops spinner before raw output
|
|
190
|
+
md_stream.push(step_info[:delta]) { |seg| @output.print seg }
|
|
191
|
+
@output.flush if @output.respond_to?(:flush)
|
|
192
|
+
when :chunk_end
|
|
193
|
+
renderer.render(step_info)
|
|
194
|
+
md_stream.finish { |seg| @output.print seg }
|
|
195
|
+
@output.flush if @output.respond_to?(:flush)
|
|
196
|
+
when :tool_call
|
|
197
|
+
@recorder&.record_tool_call(step_info[:tool], step_info[:args], call_id: step_info[:call_id])
|
|
198
|
+
formatted = renderer.render(step_info)
|
|
199
|
+
@output.puts formatted if formatted && !formatted.empty?
|
|
200
|
+
@output.flush if @output.respond_to?(:flush)
|
|
201
|
+
when :tool_result
|
|
202
|
+
@recorder&.record_tool_result(step_info[:tool], step_info[:output], status: step_info[:status] || :ok, call_id: step_info[:call_id])
|
|
203
|
+
formatted = renderer.render(step_info)
|
|
204
|
+
@output.puts formatted if formatted && !formatted.empty?
|
|
205
|
+
@output.flush if @output.respond_to?(:flush)
|
|
206
|
+
else
|
|
207
|
+
formatted = renderer.render(step_info)
|
|
208
|
+
if formatted && !formatted.empty?
|
|
209
|
+
@output.puts formatted
|
|
210
|
+
@output.flush if @output.respond_to?(:flush)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
@active_loop = orchestrator
|
|
216
|
+
res = orchestrator.run(text, conversation_history: @conversation_history)
|
|
217
|
+
|
|
218
|
+
while !res[:ok] && res[:error]&.include?("Max steps budget exceeded") && @config.fetch("agent.ask_continue_on_limit", true)
|
|
219
|
+
@output.puts "\n⚠️ [Rubik::Agent] #{res[:error]}"
|
|
220
|
+
ans = ask_yes_no(" [Rubik::Agent] +20 adım ile devam edilsin mi?", default: "n")
|
|
221
|
+
break unless ["y", "yes", "evet"].include?(ans)
|
|
222
|
+
|
|
223
|
+
@output.puts " [Rubik::Agent] Adım bütçesi +20 artırılıyor ve görev sürdürülüyor..."
|
|
224
|
+
res = orchestrator.continue_loop(20)
|
|
225
|
+
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Plan 85 D-7 (kök neden): turn içi @history'deki tool ara adımlarını
|
|
229
|
+
# conversation_history'ye kopyala — böylece continue'da model turn sırasındaki
|
|
230
|
+
# bağlamı görür ve "sıfırdan keşfe" başlamaz. res[:loop_instance].history,
|
|
231
|
+
# turn_result'ın döndürdüğü Loop örneğinin @history'sidir.
|
|
232
|
+
merge_turn_tool_history!(
|
|
233
|
+
res[:loop_instance]&.history,
|
|
234
|
+
max_entries: @config.fetch("context.merge_turn_tool_max_entries", 20).to_i
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if res[:ok]
|
|
238
|
+
record_turn_outcome!(text, res[:output].to_s, tokens: res[:tokens], streamed: res[:streamed] == true)
|
|
239
|
+
|
|
240
|
+
# Only print the final answer when it was not already streamed
|
|
241
|
+
# live; batch answers get full markdown rendering on capable TTYs.
|
|
242
|
+
# res[:streamed] reflects the FINISHING turn (chunks streamed
|
|
243
|
+
# before a finish tool call count as printed).
|
|
244
|
+
if res[:streamed] != true && !res[:output].to_s.empty?
|
|
245
|
+
rendered = Rubik::UI::MarkdownStream.render_document(res[:output], theme, enabled: md_enabled)
|
|
246
|
+
@output.puts rendered
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
@output.puts "\n[Rubik] In-memory buffer updated. Type `!diff` to view, `!apply` to save." if @buffer_manager.dirty?
|
|
250
|
+
|
|
251
|
+
# Kök-turn kroniği (root-turn planı 2026-10-04): başarılı turn da
|
|
252
|
+
# vakanüvis'e düşer — subagent'siz geçen oturumlar kroniğe girsin.
|
|
253
|
+
# Bu dalın SON deyimi olmalı: sonrasındaki bir istisna (^C, render
|
|
254
|
+
# hatası) rescue merdivenine düşüp record_failed_turn! ile İKİNCİ
|
|
255
|
+
# bir :turn_finished üretmesin — tek turn, tek kronik satırı.
|
|
256
|
+
emit_turn_finished(kind: "success", prompt: text, outcome: res[:output].to_s,
|
|
257
|
+
orchestrator: orchestrator, tokens: res[:tokens])
|
|
258
|
+
else
|
|
259
|
+
@output.puts "Görev tamamlanamadı: #{res[:error]}"
|
|
260
|
+
record_failed_turn!(text, "incomplete", res[:error], orchestrator)
|
|
261
|
+
end
|
|
262
|
+
rescue Interrupt
|
|
263
|
+
interrupted = true
|
|
264
|
+
# The user cancelled: queued wake debts settle here. Each queued
|
|
265
|
+
# notice already lives in @conversation_history (idle path injects
|
|
266
|
+
# at route time), so draining loses nothing the model needed.
|
|
267
|
+
clear_pending_wake_notices! if respond_to?(:clear_pending_wake_notices!)
|
|
268
|
+
Rubik::REPL::InterruptHandler.restore_console_after_interrupt
|
|
269
|
+
# Dedicated event type: restore_session must NOT hydrate this as a
|
|
270
|
+
# dialogue turn (the user_turn recorded at method entry already
|
|
271
|
+
# carries the question; a repl_command-style pair would duplicate it).
|
|
272
|
+
@recorder&.record("cancelled_turn",
|
|
273
|
+
"question" => text.to_s.strip[0, 80],
|
|
274
|
+
"reason" => "Interrupted by user (^C)")
|
|
275
|
+
@output.puts "\n⚠️ [Rubik::Agent] Görev kullanıcı tarafından iptal edildi (^C)."
|
|
276
|
+
record_failed_turn!(text, "cancelled", "Interrupted by user (^C)", orchestrator)
|
|
277
|
+
rescue Rubik::LLM::ClientError => e
|
|
278
|
+
# e.message may carry binary bytes (raw HTTP error bodies). Printing
|
|
279
|
+
# or recording those would raise INSIDE this rescue clause, bypassing
|
|
280
|
+
# every sibling handler and surfacing as a bare
|
|
281
|
+
# Encoding::CompatibilityError at the REPL prompt instead of the real
|
|
282
|
+
# LLM error. Sanitize before use.
|
|
283
|
+
message = Rubik::Platform.safe_utf8(e.message)
|
|
284
|
+
@output.puts "[Rubik::LLM Hatası] #{message}"
|
|
285
|
+
record_failed_turn!(text, "failed", message, orchestrator)
|
|
286
|
+
rescue StandardError => e
|
|
287
|
+
message = Rubik::Platform.safe_utf8(e.message)
|
|
288
|
+
@output.puts "[Hata] #{e.class}: #{message}"
|
|
289
|
+
record_failed_turn!(text, "failed", "#{e.class}: #{message}", orchestrator)
|
|
290
|
+
ensure
|
|
291
|
+
@active_loop = nil
|
|
292
|
+
# Flush a half-streamed markdown tail so an interrupt never eats the last
|
|
293
|
+
# line. Cleanup must never mask the in-flight exception (Interrupt, LLM
|
|
294
|
+
# error): each step reports its own failure to stderr instead of raising.
|
|
295
|
+
begin
|
|
296
|
+
md_stream&.finish { |seg| @output.print seg }
|
|
297
|
+
rescue StandardError => e
|
|
298
|
+
warn "[rubik] markdown tail flush failed: #{e.class}: #{e.message}"
|
|
299
|
+
end
|
|
300
|
+
begin
|
|
301
|
+
renderer&.spinner&.kill
|
|
302
|
+
rescue StandardError => e
|
|
303
|
+
warn "[rubik] spinner cleanup failed: #{e.class}: #{e.message}"
|
|
304
|
+
end
|
|
305
|
+
flush_subagent_notices! if respond_to?(:flush_subagent_notices!)
|
|
306
|
+
# Session review B3: notices stranded in the ended loop's inbox never
|
|
307
|
+
# reached the model channel and owed no wake — an idle maestro learned
|
|
308
|
+
# about a budget pause only at the next manual prompt (11 min in the
|
|
309
|
+
# reported session). Deliver them into history and arm the wake.
|
|
310
|
+
# Wakeloop fix 2026-10-09: NOT after an interrupt (the user cancelled;
|
|
311
|
+
# the cycle must not resume) and NOT from an autonomous notice turn
|
|
312
|
+
# (a synthetic turn must never spawn another — the prompt comes back).
|
|
313
|
+
if respond_to?(:deliver_stranded_loop_notices!)
|
|
314
|
+
deliver_stranded_loop_notices!(
|
|
315
|
+
orchestrator,
|
|
316
|
+
arm_wake: !interrupted && !@autonomous_turn
|
|
317
|
+
)
|
|
318
|
+
end
|
|
319
|
+
reap_dead_kernel_units! if respond_to?(:reap_dead_kernel_units!)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|