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,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Orchestrator
|
|
5
|
+
# Narrow façade over Loop for the extracted collaborators (Plan 87 E-1,
|
|
6
|
+
# carrying Plan 86 D-5). Collaborators reach loop-held state only through
|
|
7
|
+
# this object instead of poking `@loop` directly (Law of Demeter).
|
|
8
|
+
#
|
|
9
|
+
# Design notes:
|
|
10
|
+
# - Reads are forwarded, not re-declared: capability queries such as
|
|
11
|
+
# `deps.respond_to?(:safe_config)` must keep the exact semantics the
|
|
12
|
+
# collaborators rely on with minimal test doubles (some doubles do not
|
|
13
|
+
# define every accessor), so the surface is forwarded through
|
|
14
|
+
# respond_to_missing?/method_missing guarded by the declared SURFACE.
|
|
15
|
+
# - Writes are an explicit command surface: history rebinding and the
|
|
16
|
+
# counter/flag mutations become named methods instead of leaking
|
|
17
|
+
# Loop's attr_accessors.
|
|
18
|
+
# - `loop_instance` exists for the turn-result contract only: result
|
|
19
|
+
# hashes carry the loop object itself so the shell can drive
|
|
20
|
+
# `continue_loop` afterwards.
|
|
21
|
+
class ShellDeps
|
|
22
|
+
# Read surface: symbols collaborators consume from Loop at call time.
|
|
23
|
+
SURFACE = [
|
|
24
|
+
:history, :activity, :config, :shell, :buffer_manager, :lsp_manager,
|
|
25
|
+
:tool_registry, :provider, :budget_manager, :on_step,
|
|
26
|
+
:step_count, :max_steps, :warn_at_ratio, :context_limit, :max_output_tokens,
|
|
27
|
+
:total_tokens, :invalid_tool_count, :last_turn_streamed,
|
|
28
|
+
:context_warning_emitted, :safe_config,
|
|
29
|
+
:goal_mode?, :goal_wait, :subagent_notice_inbox, :runtime_manager
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
# Collaborator constructor shim: accepts an already-built façade or a
|
|
33
|
+
# bare loop (test doubles) and wraps the latter.
|
|
34
|
+
def self.wrap(loop_or_deps)
|
|
35
|
+
loop_or_deps.is_a?(ShellDeps) ? loop_or_deps : ShellDeps.new(loop_or_deps)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(loop)
|
|
39
|
+
@loop = loop
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Turn-result contract (see class comment): the underlying Loop object.
|
|
43
|
+
def loop_instance
|
|
44
|
+
@loop
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# ── Command surface ───────────────────────────────────────────────
|
|
48
|
+
# compact_history is pure and returns a new array, so the history
|
|
49
|
+
# reference itself must be rebound on Loop (BudgetManager compaction).
|
|
50
|
+
def replace_history!(new_history)
|
|
51
|
+
@loop.history = new_history
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def reset_invalid_tools!
|
|
55
|
+
@loop.invalid_tool_count = 0
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def record_invalid_tool!
|
|
59
|
+
@loop.invalid_tool_count += 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def mark_last_turn_streamed!(streamed)
|
|
63
|
+
@loop.last_turn_streamed = streamed
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def flag_context_warning!
|
|
67
|
+
@loop.context_warning_emitted = true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def emergency_compact_and_notify!(reason)
|
|
71
|
+
@loop.emergency_compact_and_notify!(reason)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def respond_to_missing?(name, include_private = false)
|
|
75
|
+
(SURFACE.include?(name) && @loop.respond_to?(name, include_private)) || super
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def method_missing(name, *, &)
|
|
79
|
+
return @loop.public_send(name, *, &) if SURFACE.include?(name) && @loop.respond_to?(name)
|
|
80
|
+
|
|
81
|
+
super
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "finish_message_selector"
|
|
4
|
+
require_relative "auto_apply"
|
|
5
|
+
require_relative "tool_call_id"
|
|
6
|
+
require_relative "tool_history"
|
|
7
|
+
require_relative "tool_circuit_breaker"
|
|
8
|
+
require_relative "maestro_gate"
|
|
9
|
+
require_relative "lsp_feedback"
|
|
10
|
+
|
|
11
|
+
module Rubik
|
|
12
|
+
module Orchestrator
|
|
13
|
+
# Dispatches a single normalized tool call against the registry (Plan 54
|
|
14
|
+
# D-6). The executor is a stateless collaborator: every piece of loop
|
|
15
|
+
# state (history, counters, shell wiring, streamed flag) physically stays
|
|
16
|
+
# on Loop and is reached through the ShellDeps façade (Plan 87 E-1), so
|
|
17
|
+
# send-based tests and the res[:streamed] contract keep working unchanged.
|
|
18
|
+
class StepExecutor
|
|
19
|
+
include MaestroGate
|
|
20
|
+
|
|
21
|
+
ERROR_PREFIXES = [
|
|
22
|
+
"-- ",
|
|
23
|
+
"Error:",
|
|
24
|
+
"Error ",
|
|
25
|
+
"Usage:",
|
|
26
|
+
"Pre-flight verification failed",
|
|
27
|
+
"Bilinmeyen komut",
|
|
28
|
+
"Failed",
|
|
29
|
+
"Security blocked",
|
|
30
|
+
"Command error",
|
|
31
|
+
"[EXIT ",
|
|
32
|
+
"Test run [FAILED]",
|
|
33
|
+
"Lint [FAILED]",
|
|
34
|
+
"[CONFLICT]",
|
|
35
|
+
"[CONFLICT "
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
# Plan 87 E-1: state is reached through the ShellDeps façade. Accepts a
|
|
39
|
+
# bare loop for compatibility with existing test doubles (wrapped).
|
|
40
|
+
def initialize(loop_or_deps)
|
|
41
|
+
@deps = ShellDeps.wrap(loop_or_deps)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def execute_single_tool_call(tool_call, text_resp, auto_apply_mode = nil)
|
|
45
|
+
tool_name = tool_call["tool"] || tool_call[:tool]
|
|
46
|
+
tool_args = tool_call["args"] || tool_call[:args] || {}
|
|
47
|
+
call_id = tool_call["id"] || tool_call[:id] || ToolCallId.mint(@deps.step_count, unique: true)
|
|
48
|
+
|
|
49
|
+
tool_instance = @deps.respond_to?(:tool_registry) && @deps.tool_registry ? @deps.tool_registry.get(tool_name) : nil
|
|
50
|
+
canonical_name = tool_instance ? tool_instance.name : tool_name.to_s.downcase
|
|
51
|
+
|
|
52
|
+
# Auto-apply buffer before running tests or shell commands on disk if
|
|
53
|
+
# configured (decision: AutoApply; explicit mode = test injection seam)
|
|
54
|
+
if AutoApply.apply?(@deps.config, trigger: :pre_execution, tool: canonical_name, mode: auto_apply_mode) && @deps.buffer_manager&.dirty?
|
|
55
|
+
applied = AutoApply.apply_and_sync(@deps.buffer_manager, @deps.shell)
|
|
56
|
+
@deps.on_step&.call(type: :auto_apply, reason: "pre_execution", files: applied, step: @deps.step_count, max_steps: @deps.max_steps)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return handle_finish_tool(tool_args, text_resp, auto_apply_mode) if canonical_name == "finish"
|
|
60
|
+
|
|
61
|
+
# Maestro gate: maestro'nun doğrudan yürütme araçları reddedilir
|
|
62
|
+
# (detay ve config semantics: maestro_gate.rb).
|
|
63
|
+
if maestro_gate_blocks?(canonical_name, tool_args)
|
|
64
|
+
maestro_refusal(canonical_name, tool_name, call_id)
|
|
65
|
+
nil
|
|
66
|
+
elsif tool_instance
|
|
67
|
+
execute_registered_tool(tool_instance, tool_name, tool_args, call_id, auto_apply_mode)
|
|
68
|
+
nil
|
|
69
|
+
else
|
|
70
|
+
handle_invalid_tool(tool_name, call_id)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def handle_finish_tool(tool_args, text_resp, auto_apply_mode = nil)
|
|
75
|
+
if AutoApply.apply?(@deps.config, trigger: :finish, mode: auto_apply_mode) && @deps.buffer_manager&.dirty?
|
|
76
|
+
applied = AutoApply.apply_and_sync(@deps.buffer_manager, @deps.shell)
|
|
77
|
+
@deps.on_step&.call(type: :auto_apply, reason: "finish", files: applied, step: @deps.step_count, max_steps: @deps.max_steps)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
clean_text_resp = clean_llm_text(text_resp)
|
|
81
|
+
tool_msg = (tool_args["message"] || tool_args[:message]).to_s.strip
|
|
82
|
+
|
|
83
|
+
# Review Finding 5: message-selection heuristic extracted to
|
|
84
|
+
# FinishMessageSelector (documented, named constants, unit-pinned).
|
|
85
|
+
final_msg = FinishMessageSelector.select(clean_text_resp, tool_msg)
|
|
86
|
+
|
|
87
|
+
# Plan 61: the marker must know where the content went — streamed
|
|
88
|
+
# turns already printed it above, batch turns render right below.
|
|
89
|
+
@deps.on_step&.call(type: :finish, message: final_msg, streamed: @deps.last_turn_streamed == true,
|
|
90
|
+
step: @deps.step_count, max_steps: @deps.max_steps)
|
|
91
|
+
# streamed must reflect the FINISHING turn's reality: its text may
|
|
92
|
+
# have been streamed as chunks before the finish call. A hardcoded
|
|
93
|
+
# false made the free-text render treat an already-printed answer as
|
|
94
|
+
# unprinted and print it a second time.
|
|
95
|
+
{ ok: true, output: final_msg, steps: @deps.step_count, tokens: @deps.total_tokens,
|
|
96
|
+
streamed: @deps.last_turn_streamed == true, loop_instance: @deps.loop_instance }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def execute_registered_tool(tool_instance, tool_name, tool_args, call_id, auto_apply_mode = nil)
|
|
100
|
+
@deps.on_step&.call(type: :tool_call, tool: tool_name, args: tool_args, step: @deps.step_count, max_steps: @deps.max_steps, call_id: call_id)
|
|
101
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
102
|
+
|
|
103
|
+
# Opt-in circuit breaker (G19): when enabled and the circuit for this
|
|
104
|
+
# tool is open, raise into the normal error-result plumbing below so
|
|
105
|
+
# the loop sees a regular failed tool result without executing the
|
|
106
|
+
# wedged backend again. Disabled by default.
|
|
107
|
+
breaker = circuit_breaker_for
|
|
108
|
+
blocked_by_circuit = breaker && !breaker.allow?(tool_instance.name)
|
|
109
|
+
|
|
110
|
+
tool_result = begin
|
|
111
|
+
if blocked_by_circuit
|
|
112
|
+
raise ToolCircuitBreaker::CircuitOpenError,
|
|
113
|
+
"circuit open for '#{tool_instance.name}' — repeated failures; waiting out the cooldown before retrying"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
raw_output = tool_instance.execute(tool_args)
|
|
117
|
+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
118
|
+
dur = ((t1 - t0) * 1000).round(1)
|
|
119
|
+
|
|
120
|
+
max_chars = @deps.shell&.config&.fetch("tools.max_output_chars", 12_000) || 12_000
|
|
121
|
+
max_lines = @deps.shell&.config&.fetch("tools.max_output_lines", 300) || 300
|
|
122
|
+
trunc_strat = @deps.shell&.config&.fetch("tools.truncation_strategy", "head_tail") || "head_tail"
|
|
123
|
+
|
|
124
|
+
if raw_output.is_a?(Rubik::Tools::Result)
|
|
125
|
+
trunc_res = Rubik::Tools::Truncator.truncate(raw_output.output, max_chars: max_chars, max_lines: max_lines, strategy: trunc_strat)
|
|
126
|
+
effective_dur = raw_output.duration_ms.positive? ? raw_output.duration_ms : dur
|
|
127
|
+
first_line = raw_output.output.lines.first.to_s.strip
|
|
128
|
+
first_line = nil if first_line.empty?
|
|
129
|
+
Rubik::Tools::Result.new(
|
|
130
|
+
tool_name: raw_output.tool_name.to_s.empty? ? tool_name : raw_output.tool_name,
|
|
131
|
+
call_id: raw_output.call_id.to_s.empty? ? call_id : raw_output.call_id,
|
|
132
|
+
status: raw_output.status,
|
|
133
|
+
error: raw_output.error || (raw_output.error? ? first_line : nil),
|
|
134
|
+
exit_code: raw_output.exit_code,
|
|
135
|
+
output: trunc_res[:text],
|
|
136
|
+
duration_ms: effective_dur,
|
|
137
|
+
truncated: raw_output.truncated || trunc_res[:truncated],
|
|
138
|
+
metadata: raw_output.metadata
|
|
139
|
+
)
|
|
140
|
+
elsif raw_output.respond_to?(:status) && raw_output.respond_to?(:output)
|
|
141
|
+
out_str = raw_output.output.to_s
|
|
142
|
+
trunc_res = Rubik::Tools::Truncator.truncate(out_str, max_chars: max_chars, max_lines: max_lines, strategy: trunc_strat)
|
|
143
|
+
st = raw_output.status.to_sym
|
|
144
|
+
first_line = out_str.lines.first.to_s.strip
|
|
145
|
+
first_line = nil if first_line.empty?
|
|
146
|
+
err = (raw_output.respond_to?(:error) && raw_output.error) || (st == :error ? first_line : nil)
|
|
147
|
+
Rubik::Tools::Result.new(
|
|
148
|
+
tool_name: tool_name,
|
|
149
|
+
call_id: call_id,
|
|
150
|
+
status: st,
|
|
151
|
+
error: err,
|
|
152
|
+
output: trunc_res[:text],
|
|
153
|
+
duration_ms: dur,
|
|
154
|
+
truncated: trunc_res[:truncated]
|
|
155
|
+
)
|
|
156
|
+
else
|
|
157
|
+
out_str = raw_output.to_s
|
|
158
|
+
trunc_res = Rubik::Tools::Truncator.truncate(out_str, max_chars: max_chars, max_lines: max_lines, strategy: trunc_strat)
|
|
159
|
+
|
|
160
|
+
is_err = if raw_output.respond_to?(:error?)
|
|
161
|
+
raw_output.error?
|
|
162
|
+
elsif raw_output.respond_to?(:failed?)
|
|
163
|
+
raw_output.failed?
|
|
164
|
+
elsif raw_output.is_a?(String)
|
|
165
|
+
ERROR_PREFIXES.any? { |pfx| raw_output.start_with?(pfx) }
|
|
166
|
+
else
|
|
167
|
+
false
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
first_line = out_str.lines.first.to_s.strip
|
|
171
|
+
first_line = nil if first_line.empty?
|
|
172
|
+
|
|
173
|
+
Rubik::Tools::Result.new(
|
|
174
|
+
tool_name: tool_name,
|
|
175
|
+
call_id: call_id,
|
|
176
|
+
status: is_err ? :error : :ok,
|
|
177
|
+
error: is_err ? first_line : nil,
|
|
178
|
+
output: trunc_res[:text],
|
|
179
|
+
duration_ms: dur,
|
|
180
|
+
truncated: trunc_res[:truncated]
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
185
|
+
dur = ((t1 - t0) * 1000).round(1)
|
|
186
|
+
err_msg = "Error executing tool '#{tool_name}': #{e.class} - #{e.message}"
|
|
187
|
+
Rubik::Tools::Result.new(
|
|
188
|
+
tool_name: tool_name,
|
|
189
|
+
call_id: call_id,
|
|
190
|
+
status: :error,
|
|
191
|
+
error: e.message,
|
|
192
|
+
output: err_msg,
|
|
193
|
+
duration_ms: dur
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# always-mode flush after every tool — was historically silent; the
|
|
198
|
+
# post_tool event makes the disk write visible like every other apply
|
|
199
|
+
if breaker && !blocked_by_circuit
|
|
200
|
+
if tool_result.status == :ok
|
|
201
|
+
breaker.record_success(tool_instance.name)
|
|
202
|
+
else
|
|
203
|
+
breaker.record_failure(tool_instance.name)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
if AutoApply.apply?(@deps.config, trigger: :post_tool, mode: auto_apply_mode) && @deps.buffer_manager&.dirty?
|
|
208
|
+
applied = AutoApply.apply_and_sync(@deps.buffer_manager, @deps.shell)
|
|
209
|
+
@deps.on_step&.call(type: :auto_apply, reason: "post_tool", files: applied, step: @deps.step_count, max_steps: @deps.max_steps)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Real-time LSP In-Buffer Diagnostics feedback loop (Plan 56 D-5,
|
|
213
|
+
# extracted to LspFeedback for the line budget).
|
|
214
|
+
tool_result = LspFeedback.refresh(deps: @deps, tool_instance: tool_instance,
|
|
215
|
+
tool_args: tool_args, tool_result: tool_result)
|
|
216
|
+
|
|
217
|
+
# Ekrana-bas sözleşmesi (storage-show-surface planı A): display
|
|
218
|
+
# çıktısı tam içerikle kullanıcıya :user_output olarak akar; model,
|
|
219
|
+
# history ve :tool_result özeti yalnız stub görür. Event birincil
|
|
220
|
+
# teslimat olduğu için onsuz stub yalan olur — kaynak yoksa warn.
|
|
221
|
+
if raw_output.respond_to?(:user_display?) && raw_output.user_display? && tool_result.status == :ok
|
|
222
|
+
sink = @deps.respond_to?(:on_step) ? @deps.on_step : nil
|
|
223
|
+
if sink
|
|
224
|
+
sink.call(type: :user_output, content: raw_output.to_s, label: raw_output.display_stub,
|
|
225
|
+
step: @deps.step_count, max_steps: @deps.max_steps)
|
|
226
|
+
else
|
|
227
|
+
warn("[show_file] no :user_output sink wired — display content dropped: #{raw_output.display_stub}")
|
|
228
|
+
end
|
|
229
|
+
tool_result = Rubik::Tools::Result.new(tool_name: tool_name, call_id: call_id, status: :ok,
|
|
230
|
+
output: raw_output.display_stub, duration_ms: tool_result.duration_ms)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
@deps.on_step&.call(
|
|
234
|
+
type: :tool_result,
|
|
235
|
+
tool: tool_name,
|
|
236
|
+
output: tool_result.output,
|
|
237
|
+
status: tool_result.status,
|
|
238
|
+
duration_ms: tool_result.duration_ms,
|
|
239
|
+
step: @deps.step_count,
|
|
240
|
+
max_steps: @deps.max_steps,
|
|
241
|
+
call_id: call_id,
|
|
242
|
+
line_count: tool_result.output.to_s.lines.size,
|
|
243
|
+
byte_size: tool_result.output.to_s.bytesize,
|
|
244
|
+
tokens: @deps.total_tokens
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
Rubik::Orchestrator::ToolHistory.write(@deps.history, tool_call_id: call_id, tool_name: tool_name,
|
|
248
|
+
status: tool_result.status, duration_ms: tool_result.duration_ms,
|
|
249
|
+
content: tool_result.to_xml)
|
|
250
|
+
@deps.activity.record(tool_name, tool_args, tool_result.status) if @deps.respond_to?(:activity)
|
|
251
|
+
@deps.reset_invalid_tools!
|
|
252
|
+
tool_result
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def handle_invalid_tool(tool_name, call_id)
|
|
256
|
+
@deps.record_invalid_tool!
|
|
257
|
+
@deps.activity.record(tool_name, {}, :error) if @deps.respond_to?(:activity)
|
|
258
|
+
if @deps.invalid_tool_count >= 2
|
|
259
|
+
return { ok: false, error: "Repeated invalid tool call '#{tool_name}'", steps: @deps.step_count,
|
|
260
|
+
tokens: @deps.total_tokens, loop_instance: @deps.loop_instance }
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
registry = @deps.respond_to?(:tool_registry) && @deps.tool_registry
|
|
264
|
+
all_tool_names = registry ? registry.list.map(&:name) : []
|
|
265
|
+
all_lookup_names = if registry
|
|
266
|
+
(registry.list.map(&:name) + (registry.respond_to?(:aliases_map) ? registry.aliases_map.keys : [])).uniq
|
|
267
|
+
else
|
|
268
|
+
[]
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
matched_candidates = Rubik::Tools::FuzzyMatcher.best_matches(tool_name, all_lookup_names)
|
|
272
|
+
candidate_tools = matched_candidates.map { |c| registry&.get(c)&.name || c }.uniq
|
|
273
|
+
diagnostic_msg = Rubik::Tools::DiagnosticReporter.render_unknown_tool(tool_name, candidate_tools: candidate_tools, all_tools: all_tool_names)
|
|
274
|
+
|
|
275
|
+
err_result = Rubik::Tools::Result.new(
|
|
276
|
+
tool_name: tool_name,
|
|
277
|
+
call_id: call_id,
|
|
278
|
+
status: :error,
|
|
279
|
+
error: "Tool '#{tool_name}' does not exist.",
|
|
280
|
+
output: diagnostic_msg,
|
|
281
|
+
duration_ms: 0.0
|
|
282
|
+
)
|
|
283
|
+
Rubik::Orchestrator::ToolHistory.write(@deps.history, tool_call_id: call_id, tool_name: tool_name,
|
|
284
|
+
status: :error, duration_ms: 0.0, content: err_result.to_xml)
|
|
285
|
+
nil
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
private
|
|
289
|
+
|
|
290
|
+
# G19: one breaker per executor, built from the first enabled config
|
|
291
|
+
# sighting. Threshold/cooldown changes via live !config set are picked
|
|
292
|
+
# up on the next executor instantiation — acceptable for an opt-in
|
|
293
|
+
# guard (the executor is rebuilt per loop).
|
|
294
|
+
def circuit_breaker_for
|
|
295
|
+
return @circuit_breaker if @circuit_breaker
|
|
296
|
+
|
|
297
|
+
settings = @deps.shell&.config&.fetch("tools.circuit_breaker", nil)
|
|
298
|
+
return nil unless settings.is_a?(Hash) && settings["enabled"]
|
|
299
|
+
|
|
300
|
+
@circuit_breaker = ToolCircuitBreaker.new(
|
|
301
|
+
threshold: settings.fetch("threshold", 5),
|
|
302
|
+
cooldown_seconds: settings.fetch("cooldown_seconds", 60)
|
|
303
|
+
)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def clean_llm_text(text)
|
|
307
|
+
str = text.to_s.strip
|
|
308
|
+
return "" if str.empty?
|
|
309
|
+
|
|
310
|
+
# If whole text is raw JSON tool call e.g. {"tool": "finish", "args": {...}}
|
|
311
|
+
if str.start_with?("{") && str.end_with?("}")
|
|
312
|
+
parsed = begin
|
|
313
|
+
JSON.parse(str)
|
|
314
|
+
rescue JSON::ParserError
|
|
315
|
+
nil
|
|
316
|
+
end
|
|
317
|
+
return "" if parsed.is_a?(Hash) && (parsed.key?("tool") || parsed.key?(:tool))
|
|
318
|
+
# A {...} envelope that failed to parse is itself a malformed tool
|
|
319
|
+
# call; swallow it to "" rather than leaking the raw text as content.
|
|
320
|
+
return "" if parsed.nil?
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Strip ```json ... ``` blocks
|
|
324
|
+
str.gsub(/```(?:json)?\s*\{.*?\}\s*```/m, "").strip
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../llm/chunk_assembler"
|
|
4
|
+
require_relative "provider_recovery"
|
|
5
|
+
|
|
6
|
+
module Rubik
|
|
7
|
+
module Orchestrator
|
|
8
|
+
# Streaming request execution and chunk-to-response assembly (Plan 54
|
|
9
|
+
# D-6). Stateless collaborator over Loop-held state. Overflow recovery
|
|
10
|
+
# (retry-once after emergency compaction; Interrupt, not being a
|
|
11
|
+
# StandardError, escapes to the shell's console restoration) lives in
|
|
12
|
+
# ProviderRecovery — this class keeps the chunk pipeline and keeps ALL
|
|
13
|
+
# per-attempt state inside the recovered block.
|
|
14
|
+
class StreamManager
|
|
15
|
+
# Plan 87 E-1: state is reached through the ShellDeps façade. Accepts a
|
|
16
|
+
# bare loop for compatibility with existing test doubles (wrapped).
|
|
17
|
+
def initialize(loop_or_deps)
|
|
18
|
+
@deps = ShellDeps.wrap(loop_or_deps)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute_streaming_request(opts)
|
|
22
|
+
# Recovery policy lives in ProviderRecovery; the block owns ALL
|
|
23
|
+
# per-attempt state. `retry` re-yields the block, so a failed
|
|
24
|
+
# attempt's accumulated chunks can never pollute the retried
|
|
25
|
+
# response (the Candidate 03 fix). The config guard mirrors
|
|
26
|
+
# #process_stream_chunk: bare test doubles may not expose config.
|
|
27
|
+
ProviderRecovery.with_recovery(
|
|
28
|
+
config: @deps.respond_to?(:config) ? @deps.config : nil,
|
|
29
|
+
compact: ->(reason) { @deps.emergency_compact_and_notify!(reason) }
|
|
30
|
+
) do
|
|
31
|
+
accumulated_chunks = []
|
|
32
|
+
streamed_text = false
|
|
33
|
+
res = @deps.provider.stream_chat(@deps.history, opts) do |chunk|
|
|
34
|
+
accumulated_chunks << chunk
|
|
35
|
+
streamed_text = true if process_stream_chunk(chunk)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if res.is_a?(Enumerator) && accumulated_chunks.empty?
|
|
39
|
+
res.each do |chunk|
|
|
40
|
+
accumulated_chunks << chunk
|
|
41
|
+
streamed_text = true if process_stream_chunk(chunk)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@deps.on_step&.call(type: :chunk_end, step: @deps.step_count, max_steps: @deps.max_steps) if streamed_text
|
|
46
|
+
@deps.mark_last_turn_streamed!(streamed_text)
|
|
47
|
+
build_response_from_chunks(accumulated_chunks, streamed_text: streamed_text)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build_response_from_chunks(accumulated_chunks, streamed_text: false)
|
|
52
|
+
# Plan 61 D-4: assembly moved to Rubik::LLM::ChunkAssembler (shared
|
|
53
|
+
# with BaseProvider#complete); this delegate keeps the historical
|
|
54
|
+
# call signature for tests and any external consumers.
|
|
55
|
+
Rubik::LLM::ChunkAssembler.build(
|
|
56
|
+
accumulated_chunks,
|
|
57
|
+
streamed: streamed_text == true,
|
|
58
|
+
estimate_history: -> { @deps.budget_manager.estimate_tokens_from_history },
|
|
59
|
+
estimate_text: ->(t) { @deps.provider.respond_to?(:estimate_tokens) ? @deps.provider.estimate_tokens(t) : nil },
|
|
60
|
+
on_malformed_args: lambda { |tool_name, fragment|
|
|
61
|
+
@deps.on_step&.call(
|
|
62
|
+
type: :malformed_tool_args, tool: tool_name,
|
|
63
|
+
fragment: fragment.to_s[0, 200],
|
|
64
|
+
step: @deps.step_count, max_steps: @deps.max_steps
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Forwards HTTPClient retry notifications to the shell's on_step channel
|
|
71
|
+
# as :llm_retry step_info so the REPL prints live retry lines instead of
|
|
72
|
+
# appearing frozen. Custom providers that ignore opts[:on_retry] simply
|
|
73
|
+
# produce no retry output. Loop state is read at invocation time.
|
|
74
|
+
def llm_retry_notifier
|
|
75
|
+
lambda do |attempt, max, reason|
|
|
76
|
+
@deps.on_step&.call(type: :llm_retry, attempt: attempt, max: max, reason: reason,
|
|
77
|
+
step: @deps.step_count, max_steps: @deps.max_steps)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# One chunk, both delivery forms (stream block + Enumerator replay):
|
|
84
|
+
# resolve the reasoning-capture knob, forward deltas as step events,
|
|
85
|
+
# and report whether a text delta was emitted (drives streamed_text
|
|
86
|
+
# and chunk_end).
|
|
87
|
+
def process_stream_chunk(chunk)
|
|
88
|
+
capture_reasoning = if @deps.respond_to?(:safe_config)
|
|
89
|
+
@deps.safe_config("llm.capture_reasoning", true) != false
|
|
90
|
+
elsif @deps.respond_to?(:config) && @deps.config.respond_to?(:fetch)
|
|
91
|
+
@deps.config.fetch("llm.capture_reasoning", true) != false
|
|
92
|
+
else
|
|
93
|
+
true
|
|
94
|
+
end
|
|
95
|
+
if capture_reasoning &&
|
|
96
|
+
chunk.respond_to?(:reasoning_delta?) && chunk.reasoning_delta? && !chunk.reasoning_delta.to_s.empty?
|
|
97
|
+
@deps.on_step&.call(
|
|
98
|
+
type: :llm_thought_delta,
|
|
99
|
+
delta: chunk.reasoning_delta,
|
|
100
|
+
step: @deps.step_count,
|
|
101
|
+
max_steps: @deps.max_steps
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
emitted_text = false
|
|
105
|
+
if chunk.text_delta? && !chunk.text_delta.to_s.empty?
|
|
106
|
+
emitted_text = true
|
|
107
|
+
@deps.on_step&.call(
|
|
108
|
+
type: :chunk,
|
|
109
|
+
delta: chunk.text_delta,
|
|
110
|
+
step: @deps.step_count,
|
|
111
|
+
max_steps: @deps.max_steps
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
emitted_text
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Orchestrator
|
|
5
|
+
module Subagent
|
|
6
|
+
# Birth choreography for EVERY subagent run — spawn and redeploy alike.
|
|
7
|
+
# Extracted from supervisor.rb/reuse.rb to keep both under their
|
|
8
|
+
# module-hygiene caps (the messaging.rb precedent) and, more
|
|
9
|
+
# importantly, to make the unit-registration ORDERING RULE structural
|
|
10
|
+
# instead of comment-enforced: the kernel unit is registered BEFORE
|
|
11
|
+
# start_async at both sites, with a birth state DERIVED from instance
|
|
12
|
+
# state — a unit minted while the instance is settled (the redeploy
|
|
13
|
+
# entry: completed/failed) falls back to :pending, so it can never be
|
|
14
|
+
# born dead. start_async's own @unit&.sync_state! is then the single
|
|
15
|
+
# promotion edge (pending -> active), exactly as for fresh spawns.
|
|
16
|
+
# The old dead-born trap made reap_dead_kernel_units! dispose the LIVE
|
|
17
|
+
# child ctx, silently killing the ask_question channel of the
|
|
18
|
+
# redeployed run (pinned by test_redeploy_registers_fresh_unit_without_state_error).
|
|
19
|
+
module ActivationSupervisor
|
|
20
|
+
# Call UNDER the supervisor monitor (both birth sites already ran
|
|
21
|
+
# start_async under it — the worker is parked before spawn returns).
|
|
22
|
+
# Order is load-bearing: coeffect provides first (coeffect resolution
|
|
23
|
+
# happens at execute time, but a start_async completion must never
|
|
24
|
+
# race an unprovided asker), unit registration second (pre-start, so
|
|
25
|
+
# the kernel board sees a live unit), start_async last.
|
|
26
|
+
def activate_instance!(inst, child_ctx:)
|
|
27
|
+
# ask_question'ın subagent düzlemi: instance kendi child ctx'ine
|
|
28
|
+
# asker olarak publish edilir.
|
|
29
|
+
child_ctx&.provide(Rubik::Coeffects::SUBAGENT_QUESTION_ASKER, inst)
|
|
30
|
+
# send_message atfı: subagent→maestro mesajları gerçek gönderen
|
|
31
|
+
# id'siyle taşır (coeffect yoksa root default'u yanlış atfı yapar).
|
|
32
|
+
child_ctx&.provide(Rubik::Coeffects::SUBAGENT_SENDER_ID, inst.id)
|
|
33
|
+
eff_init = @init || (@root_context.respond_to?(:get) && Rubik::Coeffects.get(@root_context, Rubik::Coeffects::INIT))
|
|
34
|
+
eff_init&.register(Kernel::SubagentUnit.new(inst, context: child_ctx, birth_state: unit_birth_state(inst)))
|
|
35
|
+
inst.start_async { |completed_inst| handle_subagent_completion(completed_inst) }
|
|
36
|
+
inst
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Child-ctx mint + the one degrade warning. A root context without
|
|
40
|
+
# #extend (duck-type host) cannot carry the question/sender
|
|
41
|
+
# coeffects — the degradation is made visible, never silent.
|
|
42
|
+
def extend_root_context!(origin)
|
|
43
|
+
child_ctx = @root_context.respond_to?(:extend) ? @root_context.extend : nil
|
|
44
|
+
Rubik::Log.warn(:orchestrator, "subagent #{origin}: root context lacks #extend — no child ctx; subagent planes degrade") unless child_ctx
|
|
45
|
+
child_ctx
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# The registration-order rule, DERIVED FROM INSTANCE STATE: a unit is
|
|
51
|
+
# born with a LIVE kernel state, never a terminal one. If the
|
|
52
|
+
# instance's current state maps through STATE_MAP into a terminal
|
|
53
|
+
# unit state (the redeploy entry: completed/failed), mint :pending
|
|
54
|
+
# instead — start_async's own @unit&.sync_state! (instance.rb) is
|
|
55
|
+
# then the single promotion edge. A born-:pending unit settling
|
|
56
|
+
# forward is a legal Unit#transition_to! edge; a born-terminal unit
|
|
57
|
+
# is frozen forever (sync_state! no-ops on dead).
|
|
58
|
+
def unit_birth_state(inst)
|
|
59
|
+
derived = Kernel::SubagentUnit::STATE_MAP.fetch(inst.state)
|
|
60
|
+
Kernel::Unit::TERMINAL_STATES.include?(derived) ? :pending : derived
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|