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,410 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../log"
|
|
4
|
+
require_relative "../orchestrator/subagent/notices"
|
|
5
|
+
require_relative "../prompt/conversation_rows"
|
|
6
|
+
require_relative "pending_notice_queue"
|
|
7
|
+
|
|
8
|
+
module Rubik
|
|
9
|
+
module REPL
|
|
10
|
+
# Plan 63 Faz C: phased initialization collaborators extracted from Shell
|
|
11
|
+
# (keeps shell.rb under the decoupling line-count gate). The Shell remains
|
|
12
|
+
# the coordinator: these methods depend on shell identity, so they live in
|
|
13
|
+
# an included module rather than in Composition (the pure builder).
|
|
14
|
+
module Lifecycle
|
|
15
|
+
# Plan 109 Faz B: notice kinds worth interrupting the idle prompt
|
|
16
|
+
# with an autonomous agent-0 turn. Excludes :context_compacted
|
|
17
|
+
# (self-healed, guidance says no action needed) and
|
|
18
|
+
# :auto_apply_failed (informational; the subagent's own next step
|
|
19
|
+
# reports it).
|
|
20
|
+
WAKE_KINDS = [:completed, :budget_exhausted, :error_paused, :message, :question, :task].freeze
|
|
21
|
+
# Phase 1: assign built subsystems. Entries derive from the
|
|
22
|
+
# ServiceCatalog (the single declaration point): one loop assigns every
|
|
23
|
+
# injectable entry's ivar, then a second pass runs the per-entry
|
|
24
|
+
# after_inject hooks (injected-buffer post-wiring — which runs only on
|
|
25
|
+
# the buffer_manager kwarg route, where Composition did not own the
|
|
26
|
+
# manager's config — and the ast_graph sync cache load). Optional
|
|
27
|
+
# entries keep their respond_to? nil-guard for older parts fixtures.
|
|
28
|
+
def inject_parts(parts, injected_buffer: false)
|
|
29
|
+
values = {}
|
|
30
|
+
ServiceCatalog.injectable.each do |entry|
|
|
31
|
+
values[entry.name] =
|
|
32
|
+
if entry.optional
|
|
33
|
+
parts.respond_to?(entry.name) ? parts[entry.name] : nil
|
|
34
|
+
else
|
|
35
|
+
parts[entry.name]
|
|
36
|
+
end
|
|
37
|
+
instance_variable_set(:"@#{entry.name}", values[entry.name])
|
|
38
|
+
end
|
|
39
|
+
# Two passes by design: after_inject hooks may reference ANY entry's
|
|
40
|
+
# value (the ast_graph cache load reads cache_store), so they run
|
|
41
|
+
# only after every ivar is assigned — combining the loops would tie
|
|
42
|
+
# hooks to catalog order.
|
|
43
|
+
ServiceCatalog.wire_after_inject(values, parts, injected_buffer: injected_buffer)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Phase 2: provide the core subsystems into the Runtime Coeffect
|
|
47
|
+
# Context. Rows derive from the ServiceCatalog; canonical (long-form)
|
|
48
|
+
# keys only — short forms ("buffer", "lsp", ...) remain readable via
|
|
49
|
+
# Rubik::Coeffects LEGACY_ALIASES for third parties.
|
|
50
|
+
def wire_runtime_context(parts)
|
|
51
|
+
ServiceCatalog.wire_rows(self, parts).each { |key, value| @runtime_manager.provide(key, value) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Phase 3: plugins declared in config (gems, drop-ins, aliases).
|
|
55
|
+
def load_plugins(parts)
|
|
56
|
+
return unless @plugin_manager
|
|
57
|
+
|
|
58
|
+
Array(@config.fetch("plugins.load", [])).each { |g| @plugin_manager.load_plugin_gem(g, runtime_manager: @runtime_manager) }
|
|
59
|
+
if @config.fetch("plugins.autoload_dropin", true) && parts.respond_to?(:plugin_storage) && parts.plugin_storage
|
|
60
|
+
@plugin_manager.load_dropin_plugins(runtime_manager: @runtime_manager, storage: parts.plugin_storage)
|
|
61
|
+
end
|
|
62
|
+
aliases_cfg = @config.fetch("plugins.aliases", {})
|
|
63
|
+
aliases_cfg.each { |s, f| @plugin_manager.register_alias(s, f) } if aliases_cfg.is_a?(Hash)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Phase 4: AST, context-index and agent-loop runtime services.
|
|
67
|
+
def register_runtime_services
|
|
68
|
+
Rubik::Runtime::ASTService.register(@runtime_manager, @ast_graph, cache_store: @cache_store)
|
|
69
|
+
Rubik::Runtime::IndexService.register(@runtime_manager, @context_index)
|
|
70
|
+
Rubik::Runtime::LoopService.register(@runtime_manager, shell: self)
|
|
71
|
+
Rubik::Storage::Service.register(@runtime_manager, Rubik::Storage::Service.build(config: @config, root_dir: @root_dir))
|
|
72
|
+
# Vakanüvis kronikçisi storage'dan HEMEN SONRA bağlanır (STORE
|
|
73
|
+
# coeffect'i çözülebilir olmalı); kendi child ctx'inde yaşar,
|
|
74
|
+
# subagents.vakanuvis.enabled=false ile tümüyle kurulmaz.
|
|
75
|
+
Rubik::Storage::Vakanuvis::Service.bind(@runtime_manager, config: @config, event_bus: @event_bus)
|
|
76
|
+
# Pruner (KÖK pencere budayıcısı, DEFAULT ON —
|
|
77
|
+
# subagents.pruner.enabled; opt-out: false): SHELL/SUPERVISOR/DMESG
|
|
78
|
+
# coeffect'leri Faz 2'den gelir; kendi child ctx'inde yaşar,
|
|
79
|
+
# prune_context aracı REQUIRES üzerinden reaktif açılır.
|
|
80
|
+
Rubik::REPL::Pruner::Service.bind(@runtime_manager, config: @config, event_bus: @event_bus)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Phase 5: build the tool registry and publish it to the runtime.
|
|
84
|
+
def build_and_provide_tool_registry
|
|
85
|
+
@tool_registry = build_tool_registry
|
|
86
|
+
@mcp_manager.tools_registry = @tool_registry if @mcp_manager
|
|
87
|
+
@subagent_supervisor.tool_registry = @tool_registry if @subagent_supervisor.respond_to?(:tool_registry=)
|
|
88
|
+
@runtime_manager.provide(:tool_registry, @tool_registry)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Phase 6: UI theme/renderer wiring into the runtime.
|
|
92
|
+
def sync_ui
|
|
93
|
+
@ui_theme = Rubik::UI::Theme.for(@output, @config)
|
|
94
|
+
@ui_renderer = Rubik::UI::RenderService.register(@runtime_manager, shell: self)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Phase 7: route :default; on resolve, satisfy the `llm` coeffect.
|
|
98
|
+
# Audit 1: rescue is intentional (credentials may be unconfigured).
|
|
99
|
+
def sync_default_llm_route
|
|
100
|
+
default_routed = @router.route(task_type: :default)
|
|
101
|
+
@runtime_manager.provide(:llm_client, default_routed[:provider]) if default_routed && default_routed[:provider]
|
|
102
|
+
@subagent_supervisor.router = @router if @subagent_supervisor.respond_to?(:router=)
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
debug_warn("Default LLM route unavailable at startup (coeffect stays pending): #{e.class}: #{e.message}")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Reload the provider catalog from config and re-route the live client —
|
|
108
|
+
# the mandatory pair after any providers.* mutation. Reactive wiring
|
|
109
|
+
# (wire_llm_change) runs it for listener-driven changes; commands run it
|
|
110
|
+
# explicitly because the Config verbs mutate the live spec hash in
|
|
111
|
+
# place, so set sees old == new and never fires the providers.*
|
|
112
|
+
# notification — dropping this "redundant-looking" refresh would
|
|
113
|
+
# silently desync the registry.
|
|
114
|
+
def refresh_llm_registry!
|
|
115
|
+
llm_registry&.load_from_config(config)
|
|
116
|
+
resync_llm_route!
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Live LLM re-route (reactive config wiring, !model/!provider switches):
|
|
120
|
+
# re-open sticky routing so the next free-text turn re-resolves with the
|
|
121
|
+
# new model (Plan 61 D-7), then re-provide the default llm_client
|
|
122
|
+
# coeffect. A failing route keeps the previous client — the same silent
|
|
123
|
+
# contract as the extracted Commands::AgentModels.sync_default_provider.
|
|
124
|
+
def resync_llm_route!
|
|
125
|
+
@conversation_route_stamp = nil
|
|
126
|
+
default_routed = begin
|
|
127
|
+
@router&.route(task_type: :default)
|
|
128
|
+
rescue StandardError => e
|
|
129
|
+
debug_warn("Live LLM re-route failed (keeping previous client): #{e.class}: #{e.message}")
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
return unless default_routed && default_routed[:provider]
|
|
133
|
+
|
|
134
|
+
@runtime_manager.provide(:llm_client, default_routed[:provider])
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Phase 8: warm-up — AST indexing, built-in commands, ambient globals,
|
|
138
|
+
# overlay loading, reactive config wiring.
|
|
139
|
+
def warm_up!
|
|
140
|
+
# nil = unset: Platform.async_knob falls back to the env-aware
|
|
141
|
+
# default (RUBIK_TEST=1 warms synchronously-off) so the schema entry
|
|
142
|
+
# never forces async warm in tests.
|
|
143
|
+
warm_async = Platform.async_knob(@config, "ast.async_warm")
|
|
144
|
+
warm_ast_graph(async: warm_async)
|
|
145
|
+
register_default_commands
|
|
146
|
+
Rubik.current_shell = self
|
|
147
|
+
Rubik.runtime_manager = @runtime_manager
|
|
148
|
+
@overlay_storage.load_all(self, @runtime_manager)
|
|
149
|
+
@config_cleanups = ConfigWiring.wire(self)
|
|
150
|
+
# Phase 5 built the tool registry; the skill tool's epoch (its
|
|
151
|
+
# revertible registration + inverse) opens on the manager's own
|
|
152
|
+
# sub-context only after config wiring so enable/disable knobs react.
|
|
153
|
+
@skill_manager&.open_epoch!(@tool_registry)
|
|
154
|
+
# mcp.async_load nil = env-aware (RUBIK_TEST=1 boots synchronously),
|
|
155
|
+
# resolved by the same Platform.async_knob home as ast.async_warm:
|
|
156
|
+
mcp_async = Platform.async_knob(@config, "mcp.async_load")
|
|
157
|
+
wire_mcp_boot_notices!(async: mcp_async)
|
|
158
|
+
@mcp_manager&.load_from_config(@config, async: mcp_async)
|
|
159
|
+
wire_subagent_events!
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Async MCP boot progress notices (mcp-async-boot D8): subscribe BEFORE
|
|
163
|
+
# the boot triggers so no event is missed, with the unsubscribe inverse
|
|
164
|
+
# registered in the same breath (wire_subagent_events! pattern). Async
|
|
165
|
+
# mode only — a synchronous boot finishes before the first prompt, so
|
|
166
|
+
# its failures already surfaced on stderr.
|
|
167
|
+
def wire_mcp_boot_notices!(async:)
|
|
168
|
+
return unless async && @event_bus
|
|
169
|
+
return if @mcp_notice_tokens # idempotent re-wire
|
|
170
|
+
|
|
171
|
+
ctx = @runtime_manager&.context
|
|
172
|
+
return unless ctx.respond_to?(:on_dispose)
|
|
173
|
+
|
|
174
|
+
@mcp_notice_tokens = [
|
|
175
|
+
[:mcp_server_loaded, @event_bus.subscribe(:mcp_server_loaded) { |payload| print_mcp_boot_notice(:loaded, payload) }],
|
|
176
|
+
[:mcp_server_failed, @event_bus.subscribe(:mcp_server_failed) { |payload| print_mcp_boot_notice(:failed, payload) }]
|
|
177
|
+
]
|
|
178
|
+
ctx.on_dispose do
|
|
179
|
+
@mcp_notice_tokens&.each { |(event, token)| @event_bus.unsubscribe(event, token) }
|
|
180
|
+
@mcp_notice_tokens = nil
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Same kworker-thread contract as handle_subagent_event: no shell locks,
|
|
185
|
+
# drop silently when the UI is not up yet (an instant boot failure is
|
|
186
|
+
# still on stderr and in dmesg). Pure display line — never enters the
|
|
187
|
+
# conversation history and never wakes an idle turn.
|
|
188
|
+
def print_mcp_boot_notice(state, payload)
|
|
189
|
+
return unless @running && @output
|
|
190
|
+
return unless payload.is_a?(Hash)
|
|
191
|
+
|
|
192
|
+
name = payload[:server]
|
|
193
|
+
line = if state == :loaded
|
|
194
|
+
"mcp: server '#{name}' ready (#{payload[:tools] || 0} tools)"
|
|
195
|
+
else
|
|
196
|
+
"mcp: server '#{name}' failed (#{payload[:error]})"
|
|
197
|
+
end
|
|
198
|
+
@terminal_guard&.print_safe_notice(line)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def wire_subagent_events!
|
|
202
|
+
return unless @event_bus
|
|
203
|
+
return if @subagent_event_tokens # idempotent re-wire (RenderService precedent)
|
|
204
|
+
|
|
205
|
+
# Inverse-by-construction: subscribe only where the unsubscribe
|
|
206
|
+
# inverse can be registered in the same breath (AGENTS.md §1A).
|
|
207
|
+
ctx = @runtime_manager&.context
|
|
208
|
+
return unless ctx.respond_to?(:on_dispose)
|
|
209
|
+
|
|
210
|
+
@subagent_notices_mutex = Mutex.new
|
|
211
|
+
@subagent_notices = []
|
|
212
|
+
# Plan 109: the idle-turn wake flag, eagerly initialized BEFORE any
|
|
213
|
+
# subscription exists so handlers never race its construction.
|
|
214
|
+
@pending_notice_queue = PendingNoticeQueue.new
|
|
215
|
+
tokens = Rubik::Orchestrator::Subagent::Notices::EVENTS.keys.map do |event|
|
|
216
|
+
[event, @event_bus.subscribe(event) { |payload| handle_subagent_event(event, payload) }]
|
|
217
|
+
end
|
|
218
|
+
tokens << [:unit_state_changed, @event_bus.subscribe(:unit_state_changed) { |payload| handle_unit_event(payload) }]
|
|
219
|
+
tokens << [:subagent_message_to_parent, @event_bus.subscribe(:subagent_message_to_parent) { |payload| handle_parent_message(payload) }]
|
|
220
|
+
@subagent_event_tokens = tokens
|
|
221
|
+
ctx.on_dispose do
|
|
222
|
+
@subagent_event_tokens&.each { |(event, token)| @event_bus.unsubscribe(event, token) }
|
|
223
|
+
@subagent_event_tokens = nil
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# ONE routing seam for subagent notices (Candidate 02, unified for
|
|
228
|
+
# Plan 109): bus event -> normalized notice -> channel-split
|
|
229
|
+
# delivery. Called on the kworker thread (bus dispatch is
|
|
230
|
+
# synchronous): Queue pushes, the standby Mutex, and the guard
|
|
231
|
+
# monitor only — no shell locks. Model channel (structured hash)
|
|
232
|
+
# enters the active loop queue and is rendered to XML at drain
|
|
233
|
+
# time; user channel (terminal line) is buffered to stdout while a
|
|
234
|
+
# loop is active and flushed at turn end. Between turns the notice
|
|
235
|
+
# is injected into @conversation_history, printed, and — when
|
|
236
|
+
# wake-worthy — queued to owe agent-0 an autonomous notice turn.
|
|
237
|
+
def handle_subagent_event(event_name, payload)
|
|
238
|
+
return unless @running && @output && !@replaying
|
|
239
|
+
|
|
240
|
+
notice = Rubik::Orchestrator::Subagent::Notices.normalize(event_name, payload)
|
|
241
|
+
route_subagent_notice(notice) if notice
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def handle_unit_event(payload)
|
|
245
|
+
return unless @running && @output && !@replaying
|
|
246
|
+
return unless payload.is_a?(Hash)
|
|
247
|
+
|
|
248
|
+
type = payload[:type]&.to_sym
|
|
249
|
+
return unless [:process, :task].include?(type)
|
|
250
|
+
|
|
251
|
+
to_state = payload[:to_state]&.to_sym
|
|
252
|
+
return unless [:completed, :failed].include?(to_state)
|
|
253
|
+
|
|
254
|
+
route_subagent_notice({ kind: :task, id: payload[:unit_id], role: "Background Task", state: to_state.to_s, reason: payload[:reason] })
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def handle_parent_message(payload)
|
|
258
|
+
return unless @running && @output && !@replaying
|
|
259
|
+
return unless payload.is_a?(Hash)
|
|
260
|
+
|
|
261
|
+
route_subagent_notice({ kind: :message, id: payload[:sender_id] || "subagent",
|
|
262
|
+
role: "Subagent", state: "message", reason: payload[:message].to_s })
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def route_subagent_notice(notice)
|
|
266
|
+
line = Rubik::Orchestrator::Subagent::Notices.user_line(notice)
|
|
267
|
+
if @active_loop
|
|
268
|
+
@active_loop.enqueue_subagent_notice(notice) if @active_loop.respond_to?(:enqueue_subagent_notice)
|
|
269
|
+
@subagent_notices_mutex&.synchronize { @subagent_notices << line }
|
|
270
|
+
else
|
|
271
|
+
inject_subagent_notice_into_conversation_history!(notice)
|
|
272
|
+
if @terminal_guard
|
|
273
|
+
@terminal_guard.print_safe_notice(line)
|
|
274
|
+
else
|
|
275
|
+
@output.puts "\n#{line}"
|
|
276
|
+
end
|
|
277
|
+
# Wakeloop fix 2026-10-09: an event landing inside an AUTONOMOUS
|
|
278
|
+
# turn's idle-path windows (before @active_loop is set, or in the
|
|
279
|
+
# ensure tail after it is cleared) must not arm a follow-up wake —
|
|
280
|
+
# that is the probabilistic residue of the chained-auto-turn bug.
|
|
281
|
+
# History injection and the printed line above still happen (no
|
|
282
|
+
# loss); only the synthetic Enter is withheld. A fresh event at a
|
|
283
|
+
# genuinely idle prompt (@autonomous_turn false) wakes as designed.
|
|
284
|
+
request_reactive_wakeup!(notice) unless @autonomous_turn
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Plan 109 Faz B: the reactive wake composition — vocabulary policy
|
|
289
|
+
# (WAKE_KINDS), kill-switch (subagents.reactive_wakeup config),
|
|
290
|
+
# state (PendingNoticeQueue), mechanism (TerminalGuard#safe_wakeup!).
|
|
291
|
+
# Every wake-worthy enqueue attempts the kick (no empty->non-empty
|
|
292
|
+
# coalescing): a kick refused earlier (user typing / paste
|
|
293
|
+
# collection) must retry on the next event, or the wake would wait
|
|
294
|
+
# for a manual Enter. TerminalGuard re-verifies emptiness under its
|
|
295
|
+
# monitor, so a typing user is never disturbed, and a surplus
|
|
296
|
+
# injected Enter degrades to the :empty noop. Runs on the kworker
|
|
297
|
+
# thread: queue push and the guard monitor only.
|
|
298
|
+
def request_reactive_wakeup!(notice)
|
|
299
|
+
return unless reactive_wakeup_enabled?
|
|
300
|
+
return unless notice.is_a?(Hash) && WAKE_KINDS.include?(notice[:kind])
|
|
301
|
+
|
|
302
|
+
pending_notice_queue.enqueue(notice)
|
|
303
|
+
@terminal_guard&.safe_wakeup!
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def reactive_wakeup_enabled?
|
|
307
|
+
@config.respond_to?(:fetch) ? @config.fetch("subagents.reactive_wakeup", true) != false : true
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Eager init happens in wire_subagent_events! (before any
|
|
311
|
+
# subscription exists); the lazy fallback keeps bare Lifecycle test
|
|
312
|
+
# doubles working (SubagentNoticeInbox mold).
|
|
313
|
+
def pending_notice_queue
|
|
314
|
+
@pending_notice_queue ||= PendingNoticeQueue.new
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def pending_wake_notices?
|
|
318
|
+
pending_notice_queue.any?
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def drain_pending_wake_notices!
|
|
322
|
+
pending_notice_queue.drain_all
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Staleness rule: any real turn's context already includes the
|
|
326
|
+
# idle-injected notices in @conversation_history — a surviving
|
|
327
|
+
# pending wake would turn the next stray Enter into a redundant
|
|
328
|
+
# notice turn.
|
|
329
|
+
def clear_pending_wake_notices!
|
|
330
|
+
pending_notice_queue.clear!
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def inject_subagent_notice_into_conversation_history!(notice)
|
|
334
|
+
xml_payload = Rubik::Orchestrator::Subagent::Notices.history_block([notice])
|
|
335
|
+
@conversation_history ||= []
|
|
336
|
+
Rubik::Prompt::ConversationRows.append_notice!(@conversation_history, xml_payload)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def flush_subagent_notices!
|
|
340
|
+
return unless @output && @subagent_notices_mutex
|
|
341
|
+
|
|
342
|
+
notices = @subagent_notices_mutex.synchronize do
|
|
343
|
+
list = @subagent_notices.dup
|
|
344
|
+
@subagent_notices.clear
|
|
345
|
+
list
|
|
346
|
+
end
|
|
347
|
+
return if notices.empty?
|
|
348
|
+
|
|
349
|
+
if @terminal_guard
|
|
350
|
+
notices.each { |m| @terminal_guard.print_safe_notice(m) }
|
|
351
|
+
else
|
|
352
|
+
@output.puts ""
|
|
353
|
+
notices.each { |m| @output.puts m }
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Session review B3 (turn-loss fix): Loop's drain runs at the TOP of each
|
|
358
|
+
# step, so notices landing during the final step stranded in the loop
|
|
359
|
+
# inbox at turn end — the user channel flushed a line, but the model
|
|
360
|
+
# channel never entered history and no wake was owed (Plan 109's
|
|
361
|
+
# documented "late-notice asymmetry" accepted the loss; a live session
|
|
362
|
+
# showed it costs a whole turn). Called from execute_free_text's ensure
|
|
363
|
+
# AFTER @active_loop = nil: the notices are injected into
|
|
364
|
+
# @conversation_history (visible to the next turn) and wake-worthy kinds
|
|
365
|
+
# arm the reactive wake, exactly like the idle-path branch of
|
|
366
|
+
# route_subagent_notice. Runs on the turn thread. Per-notice rescue is
|
|
367
|
+
# the ensure block's own contract ("cleanup must never mask the
|
|
368
|
+
# in-flight exception") — and a raising notice must not strand its
|
|
369
|
+
# queue-mates that drain_all already popped.
|
|
370
|
+
#
|
|
371
|
+
# arm_wake: false keeps the model-channel delivery but does NOT fire a
|
|
372
|
+
# synthetic Enter (wakeloop fix 2026-10-09): an INTERRUPTED turn and an
|
|
373
|
+
# AUTONOMOUS notice turn settle instead of re-arming — otherwise every
|
|
374
|
+
# stranded notice chains another auto-turn (the prompt never returns)
|
|
375
|
+
# and the cycle survives Ctrl+C.
|
|
376
|
+
def deliver_stranded_loop_notices!(loop_instance, arm_wake: true)
|
|
377
|
+
return unless loop_instance.respond_to?(:subagent_notice_inbox)
|
|
378
|
+
|
|
379
|
+
loop_instance.subagent_notice_inbox.drain_all.each do |notice|
|
|
380
|
+
inject_subagent_notice_into_conversation_history!(notice)
|
|
381
|
+
request_reactive_wakeup!(notice) if arm_wake
|
|
382
|
+
rescue StandardError => e
|
|
383
|
+
# Load-bearing visibility (wakeloop fix 2026-10-09): this delivery
|
|
384
|
+
# is the fix's "no loss" guarantee, so a dropped notice may not
|
|
385
|
+
# vanish behind the ENV-gated debug_warn. Unconditional, non-raising.
|
|
386
|
+
Rubik::Log.warn(:repl, "stranded notice delivery failed (#{notice[:kind].inspect}): " \
|
|
387
|
+
"#{e.class}: #{e.message}")
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def reap_dead_kernel_units!
|
|
392
|
+
init = @init || (@runtime_manager&.context&.get(:init) if @runtime_manager&.context.respond_to?(:get))
|
|
393
|
+
init&.reap_dead_units!
|
|
394
|
+
rescue StandardError => e
|
|
395
|
+
debug_warn("reap_dead_kernel_units failed: #{e.class}: #{e.message}")
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Audit 1: ENV["RUBIK_DEBUG"]-gated logging for intentionally swallowed
|
|
399
|
+
# exceptions (convention from config/store.rb). Never raises.
|
|
400
|
+
#
|
|
401
|
+
# $stderr.puts, not Kernel#warn: warn is silenced when $VERBOSE is nil
|
|
402
|
+
# (bundler/rake -W0). Same ruling as Log.emit and Platform.debug_warn.
|
|
403
|
+
def debug_warn(msg)
|
|
404
|
+
$stderr.puts "[WARN] #{msg}" if ENV["RUBIK_DEBUG"] # rubocop:disable Style/StderrPuts
|
|
405
|
+
rescue StandardError
|
|
406
|
+
nil
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module REPL
|
|
5
|
+
# Multi-line paste desteği (Plan 91): terminaller yapıştırılan bloğu
|
|
6
|
+
# satır-satır ENTER'larla gönderir; Windows konsolunda Reline'in
|
|
7
|
+
# bracketed-paste'i bulunmadığından her satır ayrı bir turn olarak
|
|
8
|
+
# yürütülür. Bu mixin burst algılama (zamanlama + IOGate#in_pasting?)
|
|
9
|
+
# ve explicit `!paste` modunu sağlar. Durum Shell'de yaşar
|
|
10
|
+
# (@config, @reline, @output); burada yalnızca politika kodlanır.
|
|
11
|
+
module PasteSupport
|
|
12
|
+
# Plan 91 — set by the !paste command, consumed once by
|
|
13
|
+
# read_and_process_line to enter explicit paste mode. The named API
|
|
14
|
+
# (not an ivar poke) is what keeps the commands layer out of Shell
|
|
15
|
+
# internals.
|
|
16
|
+
def request_paste!
|
|
17
|
+
@paste_requested = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Bir yapıştırma bloğunun satırlarının "aynı burst" sayılması için
|
|
21
|
+
# iki readline dönüşü arasındaki maksimum süre (ms).
|
|
22
|
+
def paste_join_window_ms
|
|
23
|
+
@config ? @config.fetch("ui.paste_join_window_ms", 120).to_f : 120.0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Monotonic saniye sayacı; testler singleton ile ezebilir.
|
|
27
|
+
def paste_clock
|
|
28
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Yapıştırma sürerken giriş tamponunda bekleyen bayt var mı?
|
|
32
|
+
# SADECE gerçek interaktif konsolda güvenilirdir: TTY olmayan
|
|
33
|
+
# ortamlarda (test harness, pipe) Windows/ANSI gate'in in_pasting?
|
|
34
|
+
# yanıltıcı true döndürebildiğinden interactive_input? şarttır.
|
|
35
|
+
def paste_pending?
|
|
36
|
+
return false unless interactive_input?
|
|
37
|
+
|
|
38
|
+
gate = @reline.const_defined?(:IOGate) ? @reline::IOGate : nil
|
|
39
|
+
return false unless gate.respond_to?(:in_pasting?)
|
|
40
|
+
|
|
41
|
+
gate.in_pasting? == true
|
|
42
|
+
rescue StandardError
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def paste_line_fast?(last_line_at, now)
|
|
47
|
+
return false if last_line_at.nil?
|
|
48
|
+
|
|
49
|
+
((now - last_line_at) * 1000.0) <= paste_join_window_ms
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Burst collection only makes sense on a real interactive console:
|
|
53
|
+
# with stubbed/piped readline (tests, RUBIK_TEST=1, non-TTY stdin)
|
|
54
|
+
# every stubbed line arrives "instantly", so an ungated collector
|
|
55
|
+
# swallows consecutive scripted lines into one turn. Test harnesses
|
|
56
|
+
# stub this method to exercise the collector deterministically.
|
|
57
|
+
def paste_burst_eligible?
|
|
58
|
+
interactive_input?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Explicit paste modu: satırları '.' satırına (veya EOF'a) kadar okur,
|
|
62
|
+
# birleşik metni döndürür. ^C Interrupt olarak yukarı sızar
|
|
63
|
+
# (read_and_process_line'in mevcut rescue'su iptali zaten ele alır).
|
|
64
|
+
# Plan 109: koleksiyon with_collecting içinde — reaktif bir uyandırma
|
|
65
|
+
# Enter'ı yapıştırılan metne boş satır eklemez.
|
|
66
|
+
def read_paste_block
|
|
67
|
+
@output.puts "[Rubik] Paste modu: metni yapıştırın, bitirmek için tek başına '.' yazın (Ctrl+C iptal)."
|
|
68
|
+
lines = []
|
|
69
|
+
collector = lambda do
|
|
70
|
+
loop do
|
|
71
|
+
line = respond_to?(:safe_readline) ? safe_readline("paste > ", add_history: false) : @reline.readline("paste > ", false)
|
|
72
|
+
break if line.nil? # EOF -> elimizdekiyle bitir
|
|
73
|
+
break if ["\x04", "\x1a"].include?(line)
|
|
74
|
+
break if line.strip == "."
|
|
75
|
+
|
|
76
|
+
lines << line
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
if @terminal_guard.respond_to?(:with_collecting)
|
|
80
|
+
@terminal_guard.with_collecting(&collector)
|
|
81
|
+
else
|
|
82
|
+
collector.call
|
|
83
|
+
end
|
|
84
|
+
lines.join("\n")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module REPL
|
|
5
|
+
# The idle-path wake queue (Plan 109 Faz B): a queue-only boundary in
|
|
6
|
+
# the Orchestrator::SubagentNoticeInbox mold — validate at entry,
|
|
7
|
+
# Queue-backed, FIFO drain with a ThreadError terminator. Deliberately
|
|
8
|
+
# distinct from the orchestrator inbox: that one is the ACTIVE-turn
|
|
9
|
+
# model-channel mailbox with run/continuation lifecycle; this one is
|
|
10
|
+
# the IDLE-turn wake flag. Its contents are advisory payload only —
|
|
11
|
+
# the authoritative copy of every notice rides @conversation_history
|
|
12
|
+
# via the idle injection; the queue answers ONE question: does the
|
|
13
|
+
# REPL owe agent-0 an autonomous notice turn?
|
|
14
|
+
#
|
|
15
|
+
# Threading contract: enqueue runs on the kworker thread (synchronous
|
|
16
|
+
# EventBus dispatch) — validation plus a bare Queue#push, never a
|
|
17
|
+
# lock a REPL thread might hold. any?/drain_all/clear! run on the
|
|
18
|
+
# REPL thread between prompts. The check-then-act window between
|
|
19
|
+
# any? and readline is benign by construction: the handler that
|
|
20
|
+
# pushes also fires TerminalGuard#safe_wakeup!, whose ungetc wakes
|
|
21
|
+
# the NEXT readline even if the current check already passed.
|
|
22
|
+
class PendingNoticeQueue
|
|
23
|
+
def initialize
|
|
24
|
+
@queue = Queue.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Entry point and the ONLY mutation path: validate, then push.
|
|
28
|
+
# Pinned message, mold style.
|
|
29
|
+
def enqueue(notice)
|
|
30
|
+
raise ArgumentError, "pending wake notice must be a Hash with :kind, got #{notice.inspect}" unless notice.is_a?(Hash) && notice[:kind]
|
|
31
|
+
|
|
32
|
+
@queue.push(notice)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def any?
|
|
36
|
+
!empty?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def empty?
|
|
40
|
+
@queue.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Pops everything pending (FIFO); a second call returns [].
|
|
44
|
+
def drain_all
|
|
45
|
+
notices = []
|
|
46
|
+
loop { notices << @queue.pop(true) }
|
|
47
|
+
rescue ThreadError
|
|
48
|
+
notices
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Staleness rule (Plan 109): any user-driven dispatch supersedes a
|
|
52
|
+
# pending wake — the notices already ride the conversation history
|
|
53
|
+
# into whatever turn runs next.
|
|
54
|
+
def clear!
|
|
55
|
+
drain_all
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|