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,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module Orchestrator
|
|
7
|
+
module Subagent
|
|
8
|
+
# Step-budget state machine for a subagent Instance (architecture-review
|
|
9
|
+
# Candidate 04): owns max_steps/current_step and their entire invariant
|
|
10
|
+
# set — initial normalization, consumption, exhaustion, the per-step
|
|
11
|
+
# visibility marker, and grant (normalize -> floor -> +=).
|
|
12
|
+
#
|
|
13
|
+
# Kinship: the main loop's Orchestrator::BudgetManager implements the
|
|
14
|
+
# 75% warning arithmetic as a STATELESS collaborator over Loop-held
|
|
15
|
+
# state, emitting XML into the main history. This class is the OWNING
|
|
16
|
+
# variant for the subagent path (different owner, different channel:
|
|
17
|
+
# a subagent-local user message). The two stay separate by the
|
|
18
|
+
# stateless-over-owner doctrine; do not merge them. Plan 109 removed
|
|
19
|
+
# the subagent-side 75% latch: transparency ([Step X/Y] every step)
|
|
20
|
+
# replaced it — the subagent paces itself, agent-0 steers.
|
|
21
|
+
#
|
|
22
|
+
# Concurrency contract: every method synchronizes on this object's
|
|
23
|
+
# Monitor. Lock order is ALWAYS instance -> budget: callers may hold
|
|
24
|
+
# the Instance monitor while calling in (grant_budget!, to_h, pause);
|
|
25
|
+
# Budget never calls back into Instance, so no inverse order exists.
|
|
26
|
+
# Grants are monotone and the worker thread is the sole consumer, so
|
|
27
|
+
# separately-locked operations compose without a global transaction.
|
|
28
|
+
class Budget
|
|
29
|
+
DEFAULT_MAX_STEPS = 88
|
|
30
|
+
DEFAULT_GRANT_STEPS = 20
|
|
31
|
+
|
|
32
|
+
def initialize(max_steps: nil)
|
|
33
|
+
@max_steps = (max_steps || DEFAULT_MAX_STEPS).to_i
|
|
34
|
+
@current_step = 0
|
|
35
|
+
@monitor = Monitor.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def max_steps
|
|
39
|
+
@monitor.synchronize { @max_steps }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def current_step
|
|
43
|
+
@monitor.synchronize { @current_step }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def exhausted?
|
|
47
|
+
@monitor.synchronize { @current_step >= @max_steps }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Enter the next step; returns the new current_step.
|
|
51
|
+
def consume
|
|
52
|
+
@monitor.synchronize { @current_step += 1 }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Per-step visibility marker (Plan 109 Faz A, replacing the 75%
|
|
56
|
+
# latch): computed under the Monitor so X/Y can never straddle a
|
|
57
|
+
# concurrent grant — grant! raises Y and the next marker shows the
|
|
58
|
+
# new total automatically. Stateless by design: only the newest
|
|
59
|
+
# marker carries information, so emergency compaction may
|
|
60
|
+
# summarize older markers away freely.
|
|
61
|
+
def step_marker
|
|
62
|
+
@monitor.synchronize { "[Step #{@current_step}/#{@max_steps}]" }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Normalize -> floor -> +=, atomically. Returns the
|
|
66
|
+
# [extra, total] pair Instance's note lambda needs. Terminal-state
|
|
67
|
+
# refusal, the :running transition, the history note, and the condvar
|
|
68
|
+
# broadcast stay on Instance — lifecycle facts, not budget facts.
|
|
69
|
+
# Called with the Instance monitor held (lock order: instance -> budget).
|
|
70
|
+
def grant!(steps)
|
|
71
|
+
@monitor.synchronize do
|
|
72
|
+
extra = Budget.normalize_grant(steps)
|
|
73
|
+
@max_steps += extra
|
|
74
|
+
[extra, @max_steps]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# One-lock snapshot: the two counters can never straddle a grant.
|
|
79
|
+
def to_h
|
|
80
|
+
@monitor.synchronize { { current_step: @current_step, max_steps: @max_steps } }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Pure half of the grant invariant, shared by grant! (mutation edge)
|
|
84
|
+
# and the reporting edges (manage_subagents steps arg, REPL
|
|
85
|
+
# !subagents extend arg): nil/non-positive -> DEFAULT_GRANT_STEPS, so
|
|
86
|
+
# the number a caller echoes can never disagree with the number
|
|
87
|
+
# grant! applies. Idempotent.
|
|
88
|
+
def self.normalize_grant(steps)
|
|
89
|
+
extra = (steps || DEFAULT_GRANT_STEPS).to_i
|
|
90
|
+
extra = DEFAULT_GRANT_STEPS if extra <= 0
|
|
91
|
+
extra
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Grant history notes (same audience as the step marker above: the
|
|
95
|
+
# subagent's own loop, not the main agent — that is why they live
|
|
96
|
+
# here rather than in Notices). Rendered by Instance's grant_budget!
|
|
97
|
+
# note lambdas.
|
|
98
|
+
def self.grant_note(extra, total)
|
|
99
|
+
"[Supervisor maestro]: Granted #{extra} additional step(s). Total budget: #{total}. Continue execution."
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.message_grant_note(extra, total)
|
|
103
|
+
"[Supervisor maestro]: Granted #{extra} additional step(s) with message. Total budget: #{total}. Continue execution."
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "notices"
|
|
4
|
+
require_relative "../auto_apply"
|
|
5
|
+
|
|
6
|
+
module Rubik
|
|
7
|
+
module Orchestrator
|
|
8
|
+
module Subagent
|
|
9
|
+
# Supervisor'ın tamamlanma koreografisi — supervisor.rb'den çıkarıldı
|
|
10
|
+
# (satır kapısı) ve bağımlılık motorunun bırakma kancası buraya
|
|
11
|
+
# bağlandı: reap'ten SONRA release_ready! çalışır — kalkan slot
|
|
12
|
+
# görünür, mezar taşlaşmış öncül get ile çözülebilir ve iptal
|
|
13
|
+
# bildirimleri öncülün kendi tamamlanma bildiriminden önce gelir
|
|
14
|
+
# (zararsız sıralama).
|
|
15
|
+
module CompletionSupervisor
|
|
16
|
+
def handle_subagent_completion(inst)
|
|
17
|
+
merge_branch_buffer(inst) if inst.workspace == "branch" && inst.completed?
|
|
18
|
+
auto_apply_subagent_buffer(inst) if inst.completed?
|
|
19
|
+
Rubik::Storage::TranscriptIndexer.index(storage_store, inst)
|
|
20
|
+
reap_orphans
|
|
21
|
+
release_ready!
|
|
22
|
+
# Result is deliberately absent: notices announce, the model retrieves
|
|
23
|
+
# via manage_subagents wait — the wait protocol stays authoritative.
|
|
24
|
+
# max_steps rides along so the budget-exhausted guidance can count.
|
|
25
|
+
# storage_id: Vakanüvis kronikçisinin annal/transcript ref anahtarı
|
|
26
|
+
# (plan 2026-10-04) — mevcut aboneler bu anahtarı yok sayar.
|
|
27
|
+
Notices.emit(@event_bus, Notices::COMPLETED_EVENT, { id: inst.id, role: inst.role, state: inst.state, max_steps: inst.max_steps, storage_id: inst.storage_id })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Completion-time spelling of the shared auto-apply policy: the
|
|
33
|
+
# subagents flag OR the buffer.auto_apply mode decides (previously
|
|
34
|
+
# this site read the flag alone and ignored the mode), and a failed
|
|
35
|
+
# apply surfaces as a notice instead of a silent rescue. The child
|
|
36
|
+
# context is already disposed here, so :shell resolves from the root.
|
|
37
|
+
def auto_apply_subagent_buffer(inst)
|
|
38
|
+
return unless AutoApply.apply?(@config, trigger: :completion, subagent: true)
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
buffer = @buffer_manager || inst.buffer_manager
|
|
42
|
+
AutoApply.apply_and_sync(buffer, Rubik::Coeffects.get(@root_context, Rubik::Coeffects::SHELL))
|
|
43
|
+
AutoApply.report_conflicts(@event_bus, buffer,
|
|
44
|
+
id: inst.id, role: inst.role, state: inst.state, trigger: :completion)
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
AutoApply.report_failure(@event_bus, id: inst.id, role: inst.role, state: inst.state,
|
|
47
|
+
trigger: :completion, reason: "#{e.class}: #{e.message}")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def merge_branch_buffer(inst)
|
|
52
|
+
return unless inst.buffer_manager && @buffer_manager && inst.buffer_manager != @buffer_manager
|
|
53
|
+
|
|
54
|
+
inst.buffer_manager.dirty_files.each_key do |path|
|
|
55
|
+
@buffer_manager.write(path, inst.buffer_manager.read(path))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubik
|
|
4
|
+
module Orchestrator
|
|
5
|
+
module Subagent
|
|
6
|
+
# Semaphore gate limiting concurrent outbound LLM requests across subagents (Plan 97).
|
|
7
|
+
# Prevents network queue exhaustion and 90s provider read timeouts under load.
|
|
8
|
+
class ConcurrencyGate
|
|
9
|
+
attr_reader :capacity
|
|
10
|
+
|
|
11
|
+
def initialize(capacity = 2)
|
|
12
|
+
@capacity = [capacity.to_i, 1].max
|
|
13
|
+
@queue = Thread::SizedQueue.new(@capacity)
|
|
14
|
+
@capacity.times { @queue.push(:token) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def synchronize
|
|
18
|
+
token = @queue.pop
|
|
19
|
+
acquired = true
|
|
20
|
+
begin
|
|
21
|
+
yield
|
|
22
|
+
ensure
|
|
23
|
+
@queue.push(token) if acquired
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def active_count
|
|
28
|
+
@capacity - @queue.size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def available_count
|
|
32
|
+
@queue.size
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Rubik
|
|
6
|
+
module Orchestrator
|
|
7
|
+
module Subagent
|
|
8
|
+
# THE single vocabulary home for subagent conversation-id spellings:
|
|
9
|
+
# the machine root id, the root recipient aliases, and the parse-side
|
|
10
|
+
# normalization of model-supplied id input. Previously the B5 dialect
|
|
11
|
+
# grammar (JSON-array-string / comma list / quoted) was class-private
|
|
12
|
+
# to ManageSubagentsTool while send_message, the supervisor plane and
|
|
13
|
+
# the storage gate each spelled "agent-0" (and scrubbed ids) on their
|
|
14
|
+
# own — the exact door B5's bug class walks through next.
|
|
15
|
+
#
|
|
16
|
+
# Naming doctrine (plan 2026-10-02): machine id "agent-0" (parent_id/
|
|
17
|
+
# sender; persisted specs round-trip it), persona "maestro" (model-
|
|
18
|
+
# visible text; renamed from "init-0" 2026-10-03 — "init-0" survives
|
|
19
|
+
# only as a legacy recipient alias). "root"/"parent" are aliases; all
|
|
20
|
+
# four route to the root plane.
|
|
21
|
+
module ConversationId
|
|
22
|
+
ROOT_ID = "agent-0"
|
|
23
|
+
ROOT_ALIASES = ["agent-0", "init-0", "root", "parent"].map(&:downcase).freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Single-id scrub: surrounding whitespace + wrapping single/double quotes.
|
|
28
|
+
def scrub(id)
|
|
29
|
+
id.to_s.strip.gsub(/\A['"]|['"]\z/, "")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def root_alias?(id)
|
|
33
|
+
ROOT_ALIASES.include?(scrub(id).downcase)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Model-supplied id lists arrive in three dialects (session review
|
|
37
|
+
# B5, observed live): a real JSON array, a JSON-encoded array STRING
|
|
38
|
+
# ('["subagent-5", "subagent-6"]' — previously ONE nonexistent id),
|
|
39
|
+
# or a comma list. Normalize all three (plus Array/single/non-id
|
|
40
|
+
# raw), strip quotes, drop empties. Byte-identical to the grammar
|
|
41
|
+
# extracted from ManageSubagentsTool#resolve_ids.
|
|
42
|
+
def normalize_list(raw)
|
|
43
|
+
list = case raw
|
|
44
|
+
when Array then raw
|
|
45
|
+
when String then parse_id_string(raw)
|
|
46
|
+
else Array(raw)
|
|
47
|
+
end
|
|
48
|
+
list.flatten.compact.map { |id| scrub(id) }.reject(&:empty?)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parse_id_string(str)
|
|
52
|
+
stripped = str.strip
|
|
53
|
+
parsed = safe_json_array(stripped) if stripped.start_with?("[")
|
|
54
|
+
parsed || (stripped.include?(",") ? stripped.split(",") : [stripped])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def safe_json_array(str)
|
|
58
|
+
parsed = JSON.parse(str)
|
|
59
|
+
parsed if parsed.is_a?(Array)
|
|
60
|
+
rescue JSON::ParserError
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
private_class_method :parse_id_string, :safe_json_array
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "budget"
|
|
4
|
+
require_relative "notices"
|
|
5
|
+
require_relative "conversation_id"
|
|
6
|
+
|
|
7
|
+
module Rubik
|
|
8
|
+
module Orchestrator
|
|
9
|
+
module Subagent
|
|
10
|
+
# PM tarzı dispatch kısıtları (kullanıcı kararı 2026-10-01): bağlı
|
|
11
|
+
# işler aynı anda ateşlenmez — FS (finish-to-start, varsayılan) öncül
|
|
12
|
+
# tamamlanınca, SS (start-to-start) öncül İLK kez running'a geçince
|
|
13
|
+
# bırakılır. Bekleyen spec bir Instance DEĞİL: thread yok, slot yok,
|
|
14
|
+
# maliyet yok — bırakılınca gerçek spawn koreografisi oynar.
|
|
15
|
+
# FF/SF v1'de bilinçli olarak desteklenmez (normalize_dep reddeder).
|
|
16
|
+
class DeferredSpec
|
|
17
|
+
attr_reader :id, :parent_id, :role, :type_name, :prompt, :system_prompt,
|
|
18
|
+
:workspace, :model, :max_steps, :deps, :provider
|
|
19
|
+
attr_accessor :state, :cancel_reason
|
|
20
|
+
|
|
21
|
+
def initialize(id:, role:, prompt:, type_name:, deps:, parent_id: ConversationId::ROOT_ID, system_prompt: nil,
|
|
22
|
+
workspace: "inherit", model: "inherit", max_steps: nil, provider: nil)
|
|
23
|
+
@id = id
|
|
24
|
+
@parent_id = parent_id
|
|
25
|
+
@role = role
|
|
26
|
+
@prompt = prompt
|
|
27
|
+
@type_name = type_name
|
|
28
|
+
@deps = deps
|
|
29
|
+
@system_prompt = system_prompt
|
|
30
|
+
@workspace = workspace
|
|
31
|
+
@model = model
|
|
32
|
+
@max_steps = max_steps
|
|
33
|
+
@provider = provider
|
|
34
|
+
@state = :queued
|
|
35
|
+
@cancel_reason = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def queued? = @state == :queued
|
|
39
|
+
def spawning? = @state == :spawning
|
|
40
|
+
def cancelled? = @state == :cancelled
|
|
41
|
+
|
|
42
|
+
# Duck-type yüzeyi: render'lar (list/status/wait) Instance ile
|
|
43
|
+
# DeferredSpec'i AYIRT ETMEDEN kullanır — dallanma yok.
|
|
44
|
+
def running? = false
|
|
45
|
+
def active? = false
|
|
46
|
+
def uptime = 0.0
|
|
47
|
+
def started_at = nil
|
|
48
|
+
def current_step = 0
|
|
49
|
+
def result = nil
|
|
50
|
+
def transcript_uri = nil
|
|
51
|
+
def last_error = @cancel_reason
|
|
52
|
+
def terminal_and_settled? = false
|
|
53
|
+
def redeployable? = false
|
|
54
|
+
# send_message'in deliverable? kapısı spec'e de değebilir (get,
|
|
55
|
+
# kuyruk fallback'i döndürür): bir spec henüz doğmadı — teslim
|
|
56
|
+
# edilemez.
|
|
57
|
+
def deliverable? = false
|
|
58
|
+
|
|
59
|
+
def depends_on = @deps.map { |d| "#{d[:id]}:#{d[:type]}" }
|
|
60
|
+
|
|
61
|
+
def to_h
|
|
62
|
+
{ id: @id, parent_id: @parent_id, role: @role, type: @type_name, state: @state.to_s,
|
|
63
|
+
depends_on: depends_on, current_step: 0, max_steps: @max_steps, uptime_seconds: 0.0,
|
|
64
|
+
started_at: nil, ended_at: nil, transcript: nil, last_error: @cancel_reason,
|
|
65
|
+
result_summary: nil, result: nil }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Saf graf fonksiyonları — kilit yok, IO yok, izole test edilebilir.
|
|
70
|
+
module DependencyGraph
|
|
71
|
+
module_function
|
|
72
|
+
|
|
73
|
+
# "subagent-1" => FS varsayılan; {id:, type:} => FS|SS zorunlu.
|
|
74
|
+
def normalize_dep(raw)
|
|
75
|
+
return { id: raw.to_s.strip, type: "FS" } if raw.is_a?(String)
|
|
76
|
+
|
|
77
|
+
id = raw.is_a?(Hash) ? (raw[:id] || raw["id"]) : nil
|
|
78
|
+
raise ArgumentError, "Invalid dependency #{raw.inspect}: expected 'subagent-N' or {id:, type: 'FS'|'SS'}" if id.nil? || id.to_s.strip.empty?
|
|
79
|
+
|
|
80
|
+
type = (raw[:type] || raw["type"] || "FS").to_s.upcase.strip
|
|
81
|
+
unless ["FS", "SS"].include?(type)
|
|
82
|
+
raise ArgumentError, "Unsupported dependency type '#{type}' (only FS and SS; " \
|
|
83
|
+
"express finish-to-finish as FS on the successor and wait on both)"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
{ id: (raw[:id] || raw["id"]).to_s.strip, type: type }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def normalize_deps(list)
|
|
90
|
+
Array(list).compact.map { |raw| normalize_dep(raw) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Üç renkli DFS; graf dışı komşular terminaldir (Instance'lar kenar
|
|
94
|
+
# taşımaz). Döngü => yol ismiyle ArgumentError (hiçbir şey rezerve
|
|
95
|
+
# edilmeden önce çağrılır — fail fast).
|
|
96
|
+
def assert_acyclic!(graph)
|
|
97
|
+
color = Hash.new(:unvisited)
|
|
98
|
+
path = []
|
|
99
|
+
graph.each_key do |start|
|
|
100
|
+
next unless color[start] == :unvisited
|
|
101
|
+
|
|
102
|
+
cycle = dfs(start, graph, color, path)
|
|
103
|
+
raise ArgumentError, "Dependency cycle detected: #{cycle.join(' -> ')}" if cycle
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def dfs(node, graph, color, path)
|
|
108
|
+
color[node] = :open
|
|
109
|
+
path << node
|
|
110
|
+
graph[node].each do |dep|
|
|
111
|
+
next unless graph.key?(dep)
|
|
112
|
+
|
|
113
|
+
return path.dup << dep if color[dep] == :open
|
|
114
|
+
|
|
115
|
+
if color[dep] == :unvisited && (cycle = dfs(dep, graph, color, path))
|
|
116
|
+
return cycle
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
path.pop
|
|
120
|
+
color[node] = :done
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Supervisor düzlemi: rezervasyon, DAG doğrulama, bırakma motoru ve
|
|
126
|
+
# iptal zinciri. Kilit sözleşmesi: @deferred mutasyonları @monitor
|
|
127
|
+
# altında; emit ASLA kilit altında değil (topla-sonra-yay).
|
|
128
|
+
module DependencySupervisor
|
|
129
|
+
# Tool girişi: spec'leri normalize et, geçici id'ler ata, DAG'ı
|
|
130
|
+
# doğrula (döngü/ölü öncül/bilinmeyen referans => ArgumentError,
|
|
131
|
+
# HİÇBİR ŞEY rezerve edilmeden), sonra rezerve et ve hazır olanları
|
|
132
|
+
# talep et. Dönen çiftler: [:launched, Instance] | [:queued, DeferredSpec].
|
|
133
|
+
def spawn_batch(specs)
|
|
134
|
+
reserved = []
|
|
135
|
+
claimed = []
|
|
136
|
+
@monitor.synchronize do
|
|
137
|
+
base = @counter
|
|
138
|
+
normalized = specs.each_with_index.map { |s, i| normalize_batch_spec(s, base + i + 1) }
|
|
139
|
+
refute_dead_dependencies!(normalized)
|
|
140
|
+
refute_unknown_dependencies!(normalized)
|
|
141
|
+
DependencyGraph.assert_acyclic!(dependency_graph(normalized))
|
|
142
|
+
normalized.each do |spec|
|
|
143
|
+
@deferred[spec.id] = spec
|
|
144
|
+
reserved << spec
|
|
145
|
+
end
|
|
146
|
+
@counter += normalized.size
|
|
147
|
+
reserved.each do |spec|
|
|
148
|
+
next unless dependencies_evaluate(spec) == :ready
|
|
149
|
+
|
|
150
|
+
spec.state = :spawning
|
|
151
|
+
claimed << spec
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
reserved.map do |spec|
|
|
156
|
+
claimed.include?(spec) ? launch_claimed!(spec) : resolve_outcome(spec)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Bırakma süpürmesi: rezervasyon sırasıyla, kuyruktakileri değerlendir.
|
|
161
|
+
# :ready => talep et (atomik), :dead => iptal (öncül failed/kayboldu),
|
|
162
|
+
# :blocked => bekle. İptal dalgaları SABİT NOKTAYA kadar kademelenir —
|
|
163
|
+
# iptal edilmiş bir öncülün kendi bağımlıları da ölür (yoksa sonsuza
|
|
164
|
+
# dek :queued sıkışır). Emit'ler kilidin DIŞINDA.
|
|
165
|
+
def release_ready!
|
|
166
|
+
claimed = []
|
|
167
|
+
cancelled = []
|
|
168
|
+
@monitor.synchronize do
|
|
169
|
+
loop do
|
|
170
|
+
wave = []
|
|
171
|
+
@deferred.each_value do |spec|
|
|
172
|
+
next unless spec.queued?
|
|
173
|
+
|
|
174
|
+
case dependencies_evaluate(spec)
|
|
175
|
+
when :ready
|
|
176
|
+
spec.state = :spawning
|
|
177
|
+
claimed << spec
|
|
178
|
+
when :dead
|
|
179
|
+
cancel_spec!(spec, "predecessor failed or is no longer tracked")
|
|
180
|
+
wave << spec
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
cancelled.concat(wave)
|
|
184
|
+
break if wave.empty?
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
announce_cancellations(cancelled)
|
|
188
|
+
claimed.each { |spec| launch_claimed!(spec) }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# wait_for'un döngü koşulu için: hedef id'ler arasında hâlâ kuyrukta
|
|
192
|
+
# bekleyen var mı? (Kuyruğa takılı zincir wait'i canlı tutar.)
|
|
193
|
+
def deferred_pending?(ids)
|
|
194
|
+
@monitor.synchronize do
|
|
195
|
+
ids.any? do |id|
|
|
196
|
+
spec = @deferred[id.to_s]
|
|
197
|
+
spec && (spec.queued? || spec.spawning?)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# spawn kuyruğunun kuyruk-sweep'i (spawn tail) spec'i bu arada
|
|
203
|
+
# bırakmış olabilir — etiketi GERÇEK duruma göre çöz (SS batch'inde
|
|
204
|
+
# "QUEUED" diyip arka planda koşan spec yanıltır).
|
|
205
|
+
def resolve_outcome(spec)
|
|
206
|
+
@monitor.synchronize do
|
|
207
|
+
inst = @instances[spec.id]
|
|
208
|
+
return [:launched, inst] if inst
|
|
209
|
+
return [:cancelled, spec] if spec.cancelled?
|
|
210
|
+
|
|
211
|
+
[:queued, spec]
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
217
|
+
# Bir spesifikasyonun bağımlılık durumu: :ready / :blocked / :dead.
|
|
218
|
+
# FS: öncül completed => hazır, failed => ölü, yoksa => bloklu.
|
|
219
|
+
# SS: öncül hiç start olmadıysa (pending) => bloklu, aksi halde hazır.
|
|
220
|
+
# Kuyrukta İPTAL edilmiş bir öncül her tip için ölüdür — yoksa
|
|
221
|
+
# bağımlıları sonsuza dek :queued sıkışır (liveness).
|
|
222
|
+
def dependencies_evaluate(spec)
|
|
223
|
+
verdicts = spec.deps.map do |d|
|
|
224
|
+
deferred_pred = @deferred[d[:id]]
|
|
225
|
+
return :dead if deferred_pred&.cancelled?
|
|
226
|
+
return :dead if deferred_pred.nil? && @instances[d[:id]].nil? && (@finished || {})[d[:id]].nil?
|
|
227
|
+
|
|
228
|
+
inst = @instances[d[:id]] || (@finished || {})[d[:id]]
|
|
229
|
+
|
|
230
|
+
if d[:type] == "FS"
|
|
231
|
+
return :dead if inst&.failed?
|
|
232
|
+
return :blocked if inst.nil? || !inst.completed?
|
|
233
|
+
elsif inst.nil? || inst.pending?
|
|
234
|
+
return :blocked
|
|
235
|
+
end
|
|
236
|
+
:ok
|
|
237
|
+
end
|
|
238
|
+
verdicts.all? { |v| v == :ok } ? :ready : :blocked
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Gerçek spawn koreografisi (child ctx, scoped registry, asker
|
|
242
|
+
# provide, start_async + fresh unit). REZERVE ID KORUNUR — modelin
|
|
243
|
+
# depends_on/wait ile referans verdiği kimlik bırakılan instance'ın
|
|
244
|
+
# kimliğidir. Başarı => spec silinir; ConcurrencyExceeded =>
|
|
245
|
+
# :queued'a geri (slot açılınca sıradaki süpürme alır); başka hata
|
|
246
|
+
# => iptal (sessiz yeniden deneme döngüsü yok — fail loud). Spawn
|
|
247
|
+
# ÖNCESİ/SONRASI kill yarışına karşı yeniden doğrulama: kill anlık
|
|
248
|
+
# :spawning spec'i işaretler, burada uyumlu hale getirilir.
|
|
249
|
+
def launch_claimed!(spec)
|
|
250
|
+
@monitor.synchronize do
|
|
251
|
+
return [:cancelled, spec] if spec.cancelled? || !@deferred.key?(spec.id)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
inst = spawn(role: spec.role, prompt: spec.prompt, type_name: spec.type_name,
|
|
255
|
+
system_prompt: spec.system_prompt, parent_id: spec.parent_id,
|
|
256
|
+
workspace: spec.workspace, model: spec.model, max_steps: spec.max_steps,
|
|
257
|
+
provider: spec.provider, reserved_id: spec.id)
|
|
258
|
+
|
|
259
|
+
killed_midflight = false
|
|
260
|
+
@monitor.synchronize do
|
|
261
|
+
killed_midflight = spec.cancelled?
|
|
262
|
+
@deferred.delete(spec.id)
|
|
263
|
+
end
|
|
264
|
+
inst&.kill if killed_midflight
|
|
265
|
+
killed_midflight ? [:cancelled, spec] : [:launched, inst]
|
|
266
|
+
rescue ConcurrencyExceededError
|
|
267
|
+
@monitor.synchronize { spec.state = :queued unless spec.cancelled? }
|
|
268
|
+
[:queued, spec]
|
|
269
|
+
rescue StandardError => e
|
|
270
|
+
cancelled = nil
|
|
271
|
+
@monitor.synchronize do
|
|
272
|
+
cancel_spec!(spec, "launch failed: #{e.class}: #{e.message}")
|
|
273
|
+
cancelled = spec
|
|
274
|
+
end
|
|
275
|
+
announce_cancellations([cancelled])
|
|
276
|
+
[:cancelled, spec]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Ölü öncülün kendi bağımlılarını da sabit noktaya kadar iptal et —
|
|
280
|
+
# iptal edilmiş spec için gelecekte süpürme tetiklenmeyecek.
|
|
281
|
+
def cancel_dependents_of(dead_ids)
|
|
282
|
+
cancelled = []
|
|
283
|
+
@monitor.synchronize do
|
|
284
|
+
until dead_ids.empty?
|
|
285
|
+
wave = @deferred.values.select do |spec|
|
|
286
|
+
spec.queued? && spec.deps.any? { |d| dead_ids.include?(d[:id]) }
|
|
287
|
+
end
|
|
288
|
+
break if wave.empty?
|
|
289
|
+
|
|
290
|
+
wave.each do |spec|
|
|
291
|
+
cancel_spec!(spec, "predecessor failed or is no longer tracked")
|
|
292
|
+
cancelled << spec
|
|
293
|
+
end
|
|
294
|
+
dead_ids = wave.map(&:id)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
cancelled
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def cancel_spec!(spec, reason)
|
|
301
|
+
spec.cancel_reason = reason
|
|
302
|
+
spec.state = :cancelled
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def announce_cancellations(specs)
|
|
306
|
+
specs.each do |spec|
|
|
307
|
+
Notices.emit(@event_bus, Notices::COMPLETED_EVENT,
|
|
308
|
+
{ id: spec.id, role: spec.role, state: :cancelled, max_steps: spec.max_steps })
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def normalize_batch_spec(raw, provisional_id)
|
|
313
|
+
role = raw[:role].to_s.strip
|
|
314
|
+
prompt = raw[:prompt].to_s.strip
|
|
315
|
+
raise ArgumentError, "Subagent role cannot be empty" if role.empty?
|
|
316
|
+
raise ArgumentError, "Subagent prompt cannot be empty" if prompt.empty?
|
|
317
|
+
|
|
318
|
+
DeferredSpec.new(
|
|
319
|
+
id: "subagent-#{provisional_id}", role: role, prompt: prompt,
|
|
320
|
+
type_name: (raw[:type_name] || "research").to_s.strip, deps: DependencyGraph.normalize_deps(raw[:depends_on]),
|
|
321
|
+
system_prompt: raw[:system_prompt], workspace: (raw[:workspace] || "inherit").to_s,
|
|
322
|
+
model: (raw[:model] || "inherit").to_s, max_steps: (raw[:max_steps] || @default_max_steps).to_i,
|
|
323
|
+
provider: raw[:provider], parent_id: (raw[:parent_id] || ConversationId::ROOT_ID).to_s
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def refute_dead_dependencies!(specs)
|
|
328
|
+
specs.each do |spec|
|
|
329
|
+
spec.deps.each do |d|
|
|
330
|
+
inst = @instances[d[:id]] || (@finished || {})[d[:id]]
|
|
331
|
+
if inst
|
|
332
|
+
# Tek tüketici: atomlar yeterli — yeni bir Instance
|
|
333
|
+
# predicate'ine gerek yok (settled? disposed'u dışlar,
|
|
334
|
+
# buradaki küme onu da içerir).
|
|
335
|
+
if d[:type] == "FS" && (inst.failed? || inst.disposed? || inst.canceling?)
|
|
336
|
+
raise ArgumentError, "FS dependency '#{d[:id]}' already #{inst.state} — a dependent on a failed predecessor is refused"
|
|
337
|
+
end
|
|
338
|
+
elsif @deferred[d[:id]]&.cancelled?
|
|
339
|
+
raise ArgumentError, "Dependency '#{d[:id]}' is cancelled — re-dispatch without it"
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def refute_unknown_dependencies!(specs)
|
|
346
|
+
known = @instances.keys + (@finished || {}).keys + @deferred.keys + specs.map(&:id)
|
|
347
|
+
specs.each do |spec|
|
|
348
|
+
spec.deps.each do |d|
|
|
349
|
+
next if known.include?(d[:id])
|
|
350
|
+
|
|
351
|
+
raise ArgumentError, "Unknown dependency '#{d[:id]}' for #{spec.id}"
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Kenarlar: spec id => bağımlılık id'leri (yalnız graf içi id'ler;
|
|
357
|
+
# Instance'lar terminal). Döngü tespiti saf DependencyGraph'ta.
|
|
358
|
+
def dependency_graph(specs)
|
|
359
|
+
graph = {}
|
|
360
|
+
(@deferred.values + specs).each { |spec| graph[spec.id] = spec.deps.map { |d| d[:id] } }
|
|
361
|
+
graph
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Kilit-altı kill çekirdeği: ASLA emit etmez, ASLA süpürme yapmaz —
|
|
365
|
+
# mutasyon + iptal listesi toplama (announce/sweep arayanın işi,
|
|
366
|
+
# kilidin dışında). :spawning spec SİLİNMEZ (launch_claimed! ile
|
|
367
|
+
# uzlaştırılır): yalnız işaretlenir, bırakma yarışı burada ölür.
|
|
368
|
+
def kill_locked(id, cascade, cancelled)
|
|
369
|
+
if (spec = @deferred[id])
|
|
370
|
+
if spec.spawning?
|
|
371
|
+
spec.cancel_reason = "killed while launching"
|
|
372
|
+
spec.state = :cancelled
|
|
373
|
+
else
|
|
374
|
+
@deferred.delete(id)
|
|
375
|
+
end
|
|
376
|
+
cancelled.concat(cancel_dependents_of([spec.id]))
|
|
377
|
+
true
|
|
378
|
+
elsif (inst = @instances[id])
|
|
379
|
+
if cascade
|
|
380
|
+
@instances.values.select { |c| c.parent_id == inst.id }.each do |child|
|
|
381
|
+
kill_locked(child.id, cascade, cancelled)
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
cancelled.concat(cancel_dependents_of([inst.id]))
|
|
385
|
+
inst.kill
|
|
386
|
+
true
|
|
387
|
+
else
|
|
388
|
+
false
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def queued_spec(id)
|
|
393
|
+
@deferred[id.to_s]
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|