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,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "reaper"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module Orchestrator
|
|
7
|
+
module Subagent
|
|
8
|
+
# Subagent reuse (context preserved redeploy): bitmiş bir subagent'a
|
|
9
|
+
# (completed/failed, thread ölmüş) aynı kimlik ve birikmiş LLM
|
|
10
|
+
# history'si ile YENİ görev verilir — taze spawn + yeniden açıklama
|
|
11
|
+
# maliyeti yok. maestro doktrininin temel taşı.
|
|
12
|
+
module Redeploy
|
|
13
|
+
# Sadece terminal VE thread'i ölmüş stateler: ölü thread, run'ın
|
|
14
|
+
# ensure bloğunun (save_transcript! → cleanup_context →
|
|
15
|
+
# on_complete) TAMAMEN çalıştığını kanıtlar — redeploy kendi
|
|
16
|
+
# tamamlanma işleyişiyle yarışamaz. settled? = :completed/:failed
|
|
17
|
+
# (Instance'ın lifecycle sözlüğü); :disposed bilinçli dışarıda.
|
|
18
|
+
def terminal_and_settled?
|
|
19
|
+
settled? && !@thread&.alive?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def redeployable?
|
|
23
|
+
terminal_and_settled? && !@archived
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Issue #55 3.2 buffer: a steering message for a finished-but-unreaped
|
|
27
|
+
# instance. Check and push under ONE monitor acquisition — the
|
|
28
|
+
# reaper's archive! shares this monitor, so an archive between a
|
|
29
|
+
# redeployable? check and the push (which would strand the message
|
|
30
|
+
# in a wiped history) cannot interleave. No grant branches here: the
|
|
31
|
+
# redeploy mints a fresh budget, and a settled instance must not
|
|
32
|
+
# resurrect.
|
|
33
|
+
def buffer_for_redeploy(message, sender: nil)
|
|
34
|
+
@monitor.synchronize do
|
|
35
|
+
return false unless redeployable?
|
|
36
|
+
|
|
37
|
+
@inbox.push(Instance::InboxMessage.new(message, sender))
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Operator diagnostics for the reap path: queued steering that died
|
|
43
|
+
# with the instance instead of being delivered by a redeploy.
|
|
44
|
+
def pending_redeploy_messages?
|
|
45
|
+
@monitor.synchronize { !@inbox.empty? }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Sadece sıfırlama — @state'i ASLA çevirme; :running flip'i ve
|
|
49
|
+
# thread başlatmayı start_async sahiplenir (flip-sonrası-start
|
|
50
|
+
# hatası penceresi olmasın). Budget taze mint edilir (bitmiş
|
|
51
|
+
# run'ın exhausted bayrağı taşıyabilir), ölü child ctx yerine yenisi
|
|
52
|
+
# ve taze scoped registry bağlanır (eskisinin ask_question'ı
|
|
53
|
+
# dispose'lu ctx'e rebind'lıydı). Yeni görev history'ye user
|
|
54
|
+
# mesajı olarak eklenir; run cycle yalnız BOŞ history seed eder.
|
|
55
|
+
def redeploy_for!(prompt:, max_steps:, context:, tool_registry:)
|
|
56
|
+
@monitor.synchronize do
|
|
57
|
+
return false unless redeployable?
|
|
58
|
+
|
|
59
|
+
drain_inbox_into_history
|
|
60
|
+
@history << { role: "user", content: "[New task from maestro]: #{prompt}" }
|
|
61
|
+
@prompt = prompt.to_s
|
|
62
|
+
@budget = Budget.new(max_steps: max_steps.to_i)
|
|
63
|
+
# @result reset is the ONE sanctioned clearer (terminal-only
|
|
64
|
+
# contract): a new lineage begins. @pause_facts rides along —
|
|
65
|
+
# the old run's wait fact must not narrate the new one.
|
|
66
|
+
@result = @last_error = @ended_at = @transcript_path = nil
|
|
67
|
+
@pause_facts = nil
|
|
68
|
+
@context = context
|
|
69
|
+
@tool_registry = tool_registry
|
|
70
|
+
@context_cleaned_up = false
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Supervisor düzlemi: spawn'ın coeffect koreografisini (child ctx,
|
|
77
|
+
# scoped registry, asker provide, SubagentUnit register, start_async)
|
|
78
|
+
# ActivationSupervisor TEK seam'den oynatır — spawn ile birebir aynı
|
|
79
|
+
# düzen: unit kaydı start_async'DEN ÖNCE, derived birth state ile
|
|
80
|
+
# (settled girişte :pending; ölü-doğan unit tuzağı yapısal olarak
|
|
81
|
+
# imkânsız — bkz. activation.rb). provider/buffer/provider_gate/
|
|
82
|
+
# storage_id/kimlik AYNEN KALIR (reuse'un kendisi bu).
|
|
83
|
+
module RedeploySupervisor
|
|
84
|
+
def redeploy(id, prompt:, max_steps: nil)
|
|
85
|
+
prompt_str = prompt.to_s.strip
|
|
86
|
+
raise ArgumentError, "Redeploy prompt cannot be empty" if prompt_str.empty?
|
|
87
|
+
|
|
88
|
+
redeployed = @monitor.synchronize do
|
|
89
|
+
key = id.to_s
|
|
90
|
+
inst = @instances[key] || (@finished || {})[key]
|
|
91
|
+
raise ArgumentError, redeploy_refusal(key, inst) unless inst&.redeployable?
|
|
92
|
+
|
|
93
|
+
validate_concurrency!
|
|
94
|
+
child_ctx = extend_root_context!(:redeploy)
|
|
95
|
+
scoped_registry = build_scoped_registry(@type_registry.get(inst.type_name), child_ctx, inst.buffer_manager)
|
|
96
|
+
return false unless inst.redeploy_for!(prompt: prompt_str, max_steps: (max_steps || @default_max_steps).to_i,
|
|
97
|
+
context: child_ctx, tool_registry: scoped_registry)
|
|
98
|
+
|
|
99
|
+
@finished&.delete(inst.id)
|
|
100
|
+
@instances[inst.id] = inst
|
|
101
|
+
activate_instance!(inst, child_ctx: child_ctx)
|
|
102
|
+
end
|
|
103
|
+
# SS bırakma kancası: redeploy da bir başlangıçtır — taze :running
|
|
104
|
+
# flip'i, bu id'yi SS ile bekleyen kuyruk spesifikasyonlarını
|
|
105
|
+
# bırakır (kilit DIŞINDA — emit kuralı; spawn ile aynı düzen).
|
|
106
|
+
release_ready!
|
|
107
|
+
redeployed
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
# Reddetme gerekçesi durum-bazlı yönlendirme içerir: maestro doğru
|
|
113
|
+
# aksiyona (steer / wait / taze spawn) yönelsin.
|
|
114
|
+
def redeploy_refusal(id, inst)
|
|
115
|
+
return "Subagent '#{id}' not found." unless inst
|
|
116
|
+
return "Subagent '#{id}' is archived (reaped) — spawn a fresh subagent via invoke_subagent." if inst.archived?
|
|
117
|
+
|
|
118
|
+
if inst.settled?
|
|
119
|
+
"Subagent '#{id}' is still settling (completion handling in flight); retry shortly."
|
|
120
|
+
else
|
|
121
|
+
"Subagent '#{id}' is #{inst.state} with a live thread — steer it (send_message / extend_budget) or kill it first."
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Orchestrator
|
|
5
|
+
module Subagent
|
|
6
|
+
# Supervisor'dan çıkarılan araç kapsamlandırıcı (satır sınırı, G-plan
|
|
7
|
+
# hygiene cap'leri): archetype filtresiyle subagent kayıt defterini
|
|
8
|
+
# kurar ve branch-workspace araçlarını yeni buffer'a bağlar.
|
|
9
|
+
module ScopedRegistry
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# K7 (plan 2026-10-04) + context-pruner (2026-10-09): kronikçinin ve
|
|
13
|
+
# budayıcının masaları yalnız kök/maestro düzlemine aittir —
|
|
14
|
+
# subagent kayıt defterlerine ('self' klonu dahil, çünkü onun
|
|
15
|
+
# filtresi yalnız spawn setini dışlar) asla kopyalanmaz. Orkestrasyon
|
|
16
|
+
# belleği tek elde kalır.
|
|
17
|
+
ROOT_ONLY_TOOLS = ["prune_context", "vakanuvis", "update_goal_ledger"].freeze
|
|
18
|
+
|
|
19
|
+
def build(source_registry:, type_def:, child_ctx:, buffer:, parent_buffer:)
|
|
20
|
+
return nil unless source_registry
|
|
21
|
+
|
|
22
|
+
new_reg = Rubik::Tools::Registry.new(coeffect_ctx: child_ctx)
|
|
23
|
+
filter = type_def.tool_filter
|
|
24
|
+
|
|
25
|
+
source_registry.list.each do |tool|
|
|
26
|
+
next if ROOT_ONLY_TOOLS.include?(tool.name)
|
|
27
|
+
next unless filter.nil? || filter.call(tool.name)
|
|
28
|
+
|
|
29
|
+
new_reg.register(bind_tool_buffer(tool, buffer, ctx: child_ctx, parent_buffer: parent_buffer))
|
|
30
|
+
end
|
|
31
|
+
new_reg
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Buffer-bound tools (rebuilt only when a branch buffer splits them
|
|
35
|
+
# off the parent); everything else is context-rebind territory.
|
|
36
|
+
BUFFER_BOUND_TOOLS = ["read_file", "write_file", "replace_file_content", "apply_buffer"].freeze
|
|
37
|
+
|
|
38
|
+
# Buffer rebinding must preserve runtime-context coeffect reads
|
|
39
|
+
# (rubik://goal/state, rubik://storage, subagent transcripts resolve
|
|
40
|
+
# through ctx): a branch-workspace coder still shares the session's
|
|
41
|
+
# live state.
|
|
42
|
+
def bind_tool_buffer(tool, buffer, ctx: nil, parent_buffer: nil)
|
|
43
|
+
return rebind_to_context(tool, ctx) unless BUFFER_BOUND_TOOLS.include?(tool.name)
|
|
44
|
+
return tool unless buffer && buffer != parent_buffer
|
|
45
|
+
|
|
46
|
+
case tool.name
|
|
47
|
+
when "read_file" then Rubik::Tools::ReadFileTool.new(buffer, runtime_context: ctx)
|
|
48
|
+
when "write_file" then Rubik::Tools::WriteFileTool.new(buffer)
|
|
49
|
+
when "replace_file_content" then Rubik::Tools::ReplaceFileContentTool.new(buffer)
|
|
50
|
+
else Rubik::Tools::ApplyBufferTool.new(buffer)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Subagent düzlemine geçen araçlar (ask_question + send_message):
|
|
55
|
+
# her ikisinin de coeffect çözümü yalnız child ctx'te sağlanır
|
|
56
|
+
# (supervisor#spawn/redeploy provide). The rebind MUST precede the
|
|
57
|
+
# buffer guard conceptually: research-type subagents inherit
|
|
58
|
+
# workspace buffers, but ask_question still has to swap to the child
|
|
59
|
+
# ctx — otherwise it would keep the ROOT tool (live shell) and pop a
|
|
60
|
+
# HUMAN dialog from the worker thread; send_message swaps so the
|
|
61
|
+
# subagent-plane message carries its REAL sender id to the maestro.
|
|
62
|
+
# Class-matched (not name-matched): a plugin/dynamic tool registered
|
|
63
|
+
# under the same name keeps its own implementation in every plane.
|
|
64
|
+
def rebind_to_context(tool, ctx)
|
|
65
|
+
return tool unless ctx.respond_to?(:get)
|
|
66
|
+
|
|
67
|
+
case tool
|
|
68
|
+
when Rubik::Tools::AskQuestionTool
|
|
69
|
+
Rubik::Tools::AskQuestionTool.new(shell: nil, runtime_context: ctx)
|
|
70
|
+
when Rubik::Tools::SendMessageTool
|
|
71
|
+
Rubik::Tools::SendMessageTool.new(runtime_context: ctx)
|
|
72
|
+
else
|
|
73
|
+
tool
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../../prompt"
|
|
5
|
+
require_relative "../../tools/truncator"
|
|
6
|
+
require_relative "../provider_recovery"
|
|
7
|
+
require_relative "../auto_apply"
|
|
8
|
+
require_relative "../tool_call_id"
|
|
9
|
+
require_relative "../tool_history"
|
|
10
|
+
require_relative "../turn_executor"
|
|
11
|
+
require_relative "../turn_executor/subagent_channel"
|
|
12
|
+
require_relative "../../coeffects"
|
|
13
|
+
require_relative "notices"
|
|
14
|
+
require_relative "resilience"
|
|
15
|
+
require_relative "conversation_id"
|
|
16
|
+
|
|
17
|
+
module Rubik
|
|
18
|
+
module Orchestrator
|
|
19
|
+
module Subagent
|
|
20
|
+
# Step execution loop, compaction evaluation, emergency recovery, and tool dispatch
|
|
21
|
+
# extracted from Instance to satisfy module hygiene and maintain vertical cohesion (Plan 95);
|
|
22
|
+
# budget state is reached only through the shared Budget collaborator (Candidate 04).
|
|
23
|
+
# Response processing itself is shared with the user channel through the TurnExecutor
|
|
24
|
+
# spine (agent-turn-executor plan); this module keeps the invocation path, budget
|
|
25
|
+
# mechanics, and the subagent dispatch.
|
|
26
|
+
module StepRunner
|
|
27
|
+
# The shared response-processing spine, built lazily on first use so
|
|
28
|
+
# objects that skip initialize (test doubles) stay safe.
|
|
29
|
+
def turn_executor
|
|
30
|
+
@turn_executor ||= Rubik::Orchestrator::TurnExecutor.new(
|
|
31
|
+
channel: Rubik::Orchestrator::TurnExecutor::SubagentChannel.new(self)
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def execute_steps_with_provider
|
|
36
|
+
turn_executor.reset!
|
|
37
|
+
loop do
|
|
38
|
+
while !@budget.exhausted? && executing?
|
|
39
|
+
@budget.consume
|
|
40
|
+
drain_inbox_into_history
|
|
41
|
+
inject_step_marker
|
|
42
|
+
evaluate_subagent_context_compaction!
|
|
43
|
+
|
|
44
|
+
opts = { tools: (@tool_registry.respond_to?(:schemas) ? @tool_registry.schemas : []) }
|
|
45
|
+
opts[:model] = @model unless @model.nil? || @model == "inherit"
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
raw_response = invoke_provider_with_recovery(opts)
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
if Resilience.transient_error?(e) && auto_pause_on_error?
|
|
51
|
+
break unless pause_on_error!(e)
|
|
52
|
+
|
|
53
|
+
next
|
|
54
|
+
end
|
|
55
|
+
raise e
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
terminal = turn_executor.call(raw_response)
|
|
59
|
+
return if terminal
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
break unless executing? && @budget.exhausted?
|
|
63
|
+
break unless pause_and_await_budget_expansion!
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Public seams for the SubagentChannel adapter (a separate object —
|
|
68
|
+
# it cannot reach this module's context). Recording the outcome and
|
|
69
|
+
# transitioning stay paired here so the lifecycle's only-mutation-
|
|
70
|
+
# path doctrine holds.
|
|
71
|
+
def complete_turn!(result)
|
|
72
|
+
@result = result
|
|
73
|
+
transition!(:completed)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def fail_turn!(reason)
|
|
77
|
+
@last_error = reason
|
|
78
|
+
transition!(:failed)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def evaluate_subagent_context_compaction!
|
|
82
|
+
return unless @context_limit&.positive?
|
|
83
|
+
|
|
84
|
+
context_cfg = ProviderRecovery.compaction_config(@config)
|
|
85
|
+
needs = Rubik::Prompt::ContextCompactor.needs_compaction?(
|
|
86
|
+
@history, context_limit: @context_limit, max_output_tokens: @max_output_tokens, config: context_cfg
|
|
87
|
+
)
|
|
88
|
+
return unless needs
|
|
89
|
+
|
|
90
|
+
compacted = Rubik::Prompt::ContextCompactor.compact_history(
|
|
91
|
+
@history, context_limit: @context_limit, max_output_tokens: @max_output_tokens, config: context_cfg
|
|
92
|
+
)
|
|
93
|
+
@history.replace(compacted)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def invoke_provider_with_recovery(opts)
|
|
97
|
+
ProviderRecovery.with_recovery(config: @config, compact: method(:emergency_compact_and_notify!)) do
|
|
98
|
+
Resilience.retry_transient(config: @config, gate: @provider_gate) do
|
|
99
|
+
invoke_provider(opts)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def auto_pause_on_error?
|
|
105
|
+
@config.respond_to?(:fetch) ? @config.fetch("subagents.auto_pause_on_network_error", true) != false : true
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Compaction actuator for the subagent path (mirrors Loop's public
|
|
109
|
+
# seam of the same name): the compaction body lives in
|
|
110
|
+
# TurnExecutor#emergency_compact! (one home for both channels); this
|
|
111
|
+
# delegates and the channel announces on the bus. Runs on the worker
|
|
112
|
+
# thread between monitor sections — never call it while holding
|
|
113
|
+
# @monitor (the emit stays lock-free).
|
|
114
|
+
def emergency_compact_and_notify!(reason)
|
|
115
|
+
turn_executor.emergency_compact!(reason)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The object that owns the fact emits it — outside the monitor, so
|
|
119
|
+
# subscribers never run under the instance lock.
|
|
120
|
+
def emit_compacted_event(reason, layer)
|
|
121
|
+
Notices.emit(@event_bus, Notices::COMPACTED_EVENT, { id: @id, role: @role, state: @state, reason: reason.to_s, layer: layer })
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def invoke_provider(opts)
|
|
125
|
+
effective_opts = opts.dup
|
|
126
|
+
effective_opts[:timeout] ||= ProviderRecovery::SUBAGENT_REQUEST_TIMEOUT
|
|
127
|
+
effective_opts[:open_timeout] ||= ProviderRecovery::DEFAULT_OPEN_TIMEOUT
|
|
128
|
+
effective_opts[:context_limit] ||= @context_limit if @context_limit
|
|
129
|
+
effective_opts[:max_tokens] ||= @max_output_tokens if @max_output_tokens&.positive?
|
|
130
|
+
|
|
131
|
+
return @provider.complete(@history, effective_opts) if @provider.respond_to?(:complete)
|
|
132
|
+
return @provider.chat(@history, **effective_opts) if @provider.respond_to?(:chat)
|
|
133
|
+
|
|
134
|
+
raise "Provider #{@provider.class} does not support complete or chat"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def drain_inbox_into_history
|
|
138
|
+
until @inbox.empty?
|
|
139
|
+
begin
|
|
140
|
+
entry = @inbox.pop(true)
|
|
141
|
+
@history << { role: "user", content: "[Message from #{sender_label(entry)}]: #{entry.text}" } if entry
|
|
142
|
+
rescue ThreadError
|
|
143
|
+
break
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# İki-düzlem sözleşme: gönderen yoksa ya da makine adı agent-0'sa
|
|
149
|
+
# model persona'sı maestro ile etiketlenir; subagent gönderenler
|
|
150
|
+
# (sibling mesajları) kendi id'leriyle görünür.
|
|
151
|
+
def sender_label(entry)
|
|
152
|
+
return "maestro" if entry.from.nil? || entry.from == ConversationId::ROOT_ID
|
|
153
|
+
|
|
154
|
+
entry.from
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Single-call subagent dispatch (the TurnExecutor's per-call seam):
|
|
158
|
+
# pre-execution auto-apply, timed execution, and the canonical
|
|
159
|
+
# ToolHistory row — which now carries status + duration_ms like every
|
|
160
|
+
# other channel (the row-superset delta of the agent-turn-executor
|
|
161
|
+
# plan; no wire-side formatter reads them, observability only).
|
|
162
|
+
def execute_subagent_tool_call(tool_call)
|
|
163
|
+
name = tool_call["tool"]
|
|
164
|
+
args = tool_call["args"] || {}
|
|
165
|
+
tool = @tool_registry.respond_to?(:get) ? @tool_registry.get(name) : nil
|
|
166
|
+
canonical_name = tool ? tool.name : name.to_s.downcase
|
|
167
|
+
|
|
168
|
+
auto_apply_buffer!(:pre_execution, tool: canonical_name)
|
|
169
|
+
|
|
170
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
171
|
+
output, status = dispatch_subagent_tool(tool, name, args)
|
|
172
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(1)
|
|
173
|
+
Rubik::Orchestrator::ToolHistory.write(@history, tool_call_id: tool_call["id"], tool_name: name,
|
|
174
|
+
status: status, duration_ms: duration_ms, content: output.to_s)
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# The subagent's own spelling of the shared auto-apply policy (AutoApply
|
|
179
|
+
# owns the decision): decide, apply+sync through the coeffect shell,
|
|
180
|
+
# and REPORT failures instead of swallowing them — a failed pre-test
|
|
181
|
+
# apply used to mean tests silently ran on stale disk. The decision
|
|
182
|
+
# sits OUTSIDE the rescue: a policy bug must fail the subagent as a
|
|
183
|
+
# code error, not masquerade as a runtime apply failure. Runs on the
|
|
184
|
+
# worker thread between monitor sections — never call it while
|
|
185
|
+
# holding @monitor (same discipline as emergency_compact_and_notify!).
|
|
186
|
+
def auto_apply_buffer!(trigger, tool: nil)
|
|
187
|
+
return unless AutoApply.apply?(@config, trigger: trigger, subagent: true, tool: tool)
|
|
188
|
+
|
|
189
|
+
begin
|
|
190
|
+
AutoApply.apply_and_sync(@buffer_manager, coeffect_shell)
|
|
191
|
+
AutoApply.report_conflicts(@event_bus, @buffer_manager,
|
|
192
|
+
id: @id, role: @role, state: @state, trigger: trigger)
|
|
193
|
+
rescue StandardError => e
|
|
194
|
+
AutoApply.report_failure(@event_bus, id: @id, role: @role, state: @state,
|
|
195
|
+
trigger: trigger, reason: "#{e.class}: #{e.message}")
|
|
196
|
+
# The subagent itself is the actor about to trust the disk (its
|
|
197
|
+
# next step is usually run_tests) — a parent notice alone keeps
|
|
198
|
+
# the one participant who could react in the dark.
|
|
199
|
+
@history << { role: "user",
|
|
200
|
+
content: "[AutoApply] buffer auto-apply (#{trigger}) failed: #{e.message}" }
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# The subagent's own child context walks the parent chain to the
|
|
205
|
+
# root-provided :shell, so its applies sync the AST index exactly
|
|
206
|
+
# like every main-path apply (previously this path never synced).
|
|
207
|
+
def coeffect_shell
|
|
208
|
+
Rubik::Coeffects.get(@context, Rubik::Coeffects::SHELL)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Returns [truncated_output, status] so the dispatch site can build
|
|
212
|
+
# the canonical tool row. Unknown tools and execution errors carry
|
|
213
|
+
# :error; plain results are :ok (Result-backed tools carry their own).
|
|
214
|
+
def dispatch_subagent_tool(tool, name, args)
|
|
215
|
+
return ["Tool '#{name}' is not available in subagent toolset.", :error] unless tool
|
|
216
|
+
|
|
217
|
+
raw = tool.execute(args)
|
|
218
|
+
out_str = raw.respond_to?(:output) ? raw.output.to_s : raw.to_s
|
|
219
|
+
max_chars = (@config.respond_to?(:fetch) ? @config.fetch("tools.max_output_chars", 12_000) : 12_000) || 12_000
|
|
220
|
+
max_lines = (@config.respond_to?(:fetch) ? @config.fetch("tools.max_output_lines", 300) : 300) || 300
|
|
221
|
+
trunc = Rubik::Tools::Truncator.truncate(out_str, max_chars: max_chars.to_i, max_lines: max_lines.to_i)
|
|
222
|
+
status = raw.respond_to?(:status) ? raw.status : :ok
|
|
223
|
+
[trunc[:text], status]
|
|
224
|
+
rescue StandardError => e
|
|
225
|
+
["Tool execution error: #{e.class}: #{e.message}", :error]
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
# Plan 109 Faz A: per-step budget transparency replaces the 75%
|
|
231
|
+
# warning latch — Budget owns the marker string under its lock; the
|
|
232
|
+
# step loop owns only the history mutation (a conversation fact),
|
|
233
|
+
# the same split the warning used.
|
|
234
|
+
def inject_step_marker
|
|
235
|
+
@history << { role: "user", content: @budget.step_marker }
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Orchestrator
|
|
5
|
+
module Subagent
|
|
6
|
+
# Mints process-unique transcript storage ids (Plan 110, closes #48).
|
|
7
|
+
#
|
|
8
|
+
# Display ids restart at "subagent-1" in every process, so bare display
|
|
9
|
+
# ids collide across sessions sharing a workspace: transcript paths
|
|
10
|
+
# derived from them let one session's save clobber another's, or serve
|
|
11
|
+
# a dead run's file as live evidence (2026-09-27 phantom-subagent
|
|
12
|
+
# incident). A storage id suffixes the display id with three factors:
|
|
13
|
+
# * pid — two live processes can never mint the same id;
|
|
14
|
+
# * epoch seconds — same-process re-runs (test suites) differ;
|
|
15
|
+
# * rand4 — same-second re-mints stay distinct.
|
|
16
|
+
# Unlike ToolCallId — whose doc scopes it to tool-call id spellings and
|
|
17
|
+
# whose bounded session histories make rand-only viable — a transcript
|
|
18
|
+
# filesystem accumulates forever, so uniqueness needs pid + time.
|
|
19
|
+
module StorageId
|
|
20
|
+
def self.mint(display_id)
|
|
21
|
+
"#{display_id}-#{Process.pid}-#{Time.now.to_i}-#{rand(1000..9999)}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|