alysis-code 0.13.0__py3-none-any.whl
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.
- alysis_code/__init__.py +3 -0
- alysis_code/__main__.py +4 -0
- alysis_code/_build_info.py +14 -0
- alysis_code/account_login.py +468 -0
- alysis_code/agent/README.md +35 -0
- alysis_code/agent/__init__.py +11 -0
- alysis_code/agent/acceptance_contract.py +2217 -0
- alysis_code/agent/blast_radius.py +1403 -0
- alysis_code/agent/cache_keepalive.py +227 -0
- alysis_code/agent/completion_certificate.py +366 -0
- alysis_code/agent/completion_gate.py +306 -0
- alysis_code/agent/empty_response_stall.py +403 -0
- alysis_code/agent/errors.py +28 -0
- alysis_code/agent/llm_calls.py +475 -0
- alysis_code/agent/mutation_classification.py +227 -0
- alysis_code/agent/prompt_context.py +2508 -0
- alysis_code/agent/read_ledger.py +253 -0
- alysis_code/agent/regression_baseline.py +642 -0
- alysis_code/agent/reproduction_first.py +610 -0
- alysis_code/agent/sensitive_output.py +629 -0
- alysis_code/agent/session.py +3218 -0
- alysis_code/agent/steering.py +191 -0
- alysis_code/agent/subagent_execution.py +5177 -0
- alysis_code/agent/subagent_workspace.py +666 -0
- alysis_code/agent/tools_assembly.py +4728 -0
- alysis_code/agent/turn/__init__.py +101 -0
- alysis_code/agent/turn/core.py +8483 -0
- alysis_code/agent/turn/events.py +113 -0
- alysis_code/agent/turn/exploration.py +590 -0
- alysis_code/agent/turn/interventions.py +65 -0
- alysis_code/agent/turn/read_cache.py +420 -0
- alysis_code/agent/turn/snapshot.py +179 -0
- alysis_code/agent/turn_contract.py +661 -0
- alysis_code/agent/turn_path.py +129 -0
- alysis_code/agent/verification.py +2885 -0
- alysis_code/agent/verification_commands.py +512 -0
- alysis_code/agent/verification_evidence.py +738 -0
- alysis_code/agent_loop.py +693 -0
- alysis_code/agent_runtimes/__init__.py +51 -0
- alysis_code/agent_runtimes/base.py +114 -0
- alysis_code/agent_runtimes/builtins.py +129 -0
- alysis_code/agent_runtimes/codex_cli.py +664 -0
- alysis_code/agent_runtimes/host.py +263 -0
- alysis_code/agent_runtimes/registry.py +64 -0
- alysis_code/agent_runtimes/service.py +150 -0
- alysis_code/agentbox_client.py +416 -0
- alysis_code/agentbox_integration.py +310 -0
- alysis_code/alysis_cloud.py +152 -0
- alysis_code/approval_scope.py +276 -0
- alysis_code/assets/README.md +33 -0
- alysis_code/assets/__init__.py +126 -0
- alysis_code/assets/asset_read_core.py +281 -0
- alysis_code/assets/budget_allocator.py +456 -0
- alysis_code/assets/comprehender.py +759 -0
- alysis_code/assets/index.py +654 -0
- alysis_code/assets/ingestion.py +275 -0
- alysis_code/assets/legacy_migration.py +413 -0
- alysis_code/assets/models.py +263 -0
- alysis_code/assets/ocr.py +239 -0
- alysis_code/assets/owl/ascii/f-000.txt +13 -0
- alysis_code/assets/owl/ascii/f-001.txt +13 -0
- alysis_code/assets/owl/ascii/f-002.txt +13 -0
- alysis_code/assets/owl/ascii/f-003.txt +13 -0
- alysis_code/assets/owl/ascii/f-004.txt +13 -0
- alysis_code/assets/owl/ascii/f-005.txt +13 -0
- alysis_code/assets/owl/ascii/f-006.txt +13 -0
- alysis_code/assets/owl/ascii/f-007.txt +13 -0
- alysis_code/assets/owl/ascii/f-008.txt +13 -0
- alysis_code/assets/owl/ascii/f-009.txt +13 -0
- alysis_code/assets/owl/ascii/f-010.txt +13 -0
- alysis_code/assets/owl/ascii/f-011.txt +13 -0
- alysis_code/assets/owl/ascii/f-012.txt +13 -0
- alysis_code/assets/owl/ascii/f-013.txt +13 -0
- alysis_code/assets/owl/ascii/f-014.txt +13 -0
- alysis_code/assets/owl/ascii/f-015.txt +13 -0
- alysis_code/assets/owl/ascii/f-016.txt +13 -0
- alysis_code/assets/owl/ascii/f-017.txt +13 -0
- alysis_code/assets/owl/ascii/f-018.txt +13 -0
- alysis_code/assets/owl/ascii/f-019.txt +13 -0
- alysis_code/assets/owl/ascii/f-020.txt +13 -0
- alysis_code/assets/owl/index.html +98 -0
- alysis_code/assets/owl/show-owl.sh +761 -0
- alysis_code/assets/paths.py +49 -0
- alysis_code/assets/plan_binding.py +326 -0
- alysis_code/assets/planner_context.py +466 -0
- alysis_code/assets/planner_tools.py +184 -0
- alysis_code/assets/prompts.py +101 -0
- alysis_code/assets/replanner_context.py +239 -0
- alysis_code/assets/surface.py +521 -0
- alysis_code/assets/untrusted_content.py +48 -0
- alysis_code/assets/usage_logger.py +94 -0
- alysis_code/assets/worker_mirror.py +428 -0
- alysis_code/assets/worker_section.py +303 -0
- alysis_code/assets/worker_tools.py +468 -0
- alysis_code/atomic_io.py +83 -0
- alysis_code/auth_diagnostics.py +272 -0
- alysis_code/background_runner.py +366 -0
- alysis_code/branding.py +270 -0
- alysis_code/budget_policy.py +390 -0
- alysis_code/build_identity.py +465 -0
- alysis_code/builtin_hooks/__init__.py +7 -0
- alysis_code/builtin_hooks/notify_done_windows.py +65 -0
- alysis_code/bwrap_etc.py +76 -0
- alysis_code/cancellation.py +41 -0
- alysis_code/capabilities.py +137 -0
- alysis_code/chatgpt_codex_static_provider.py +133 -0
- alysis_code/cli.py +51 -0
- alysis_code/cli_impl/__init__.py +1 -0
- alysis_code/cli_impl/assets_cli.py +537 -0
- alysis_code/cli_impl/assets_modal.py +412 -0
- alysis_code/cli_impl/chat/__init__.py +156 -0
- alysis_code/cli_impl/chat/commands.py +2616 -0
- alysis_code/cli_impl/chat/loop.py +4508 -0
- alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
- alysis_code/cli_impl/chat/rendering.py +444 -0
- alysis_code/cli_impl/chat/state.py +124 -0
- alysis_code/cli_impl/chat_resume.py +830 -0
- alysis_code/cli_impl/chat_slash_completer.py +258 -0
- alysis_code/cli_impl/commands/__init__.py +11 -0
- alysis_code/cli_impl/commands/_shared.py +89 -0
- alysis_code/cli_impl/commands/auth.py +623 -0
- alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
- alysis_code/cli_impl/commands/chat_state.py +158 -0
- alysis_code/cli_impl/commands/chat_status.py +1248 -0
- alysis_code/cli_impl/commands/chat_terminal.py +942 -0
- alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
- alysis_code/cli_impl/commands/cli_common.py +1223 -0
- alysis_code/cli_impl/commands/cli_surface.py +77 -0
- alysis_code/cli_impl/commands/config.py +131 -0
- alysis_code/cli_impl/commands/conventions.py +85 -0
- alysis_code/cli_impl/commands/execution_helpers.py +350 -0
- alysis_code/cli_impl/commands/extensions.py +401 -0
- alysis_code/cli_impl/commands/forge.py +1282 -0
- alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
- alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
- alysis_code/cli_impl/commands/hooks.py +737 -0
- alysis_code/cli_impl/commands/ide_bridge.py +31 -0
- alysis_code/cli_impl/commands/mcp.py +700 -0
- alysis_code/cli_impl/commands/profile.py +453 -0
- alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
- alysis_code/cli_impl/commands/report.py +88 -0
- alysis_code/cli_impl/commands/root.py +1118 -0
- alysis_code/cli_impl/commands/sandbox.py +184 -0
- alysis_code/cli_impl/commands/server.py +54 -0
- alysis_code/cli_impl/commands/sessions.py +252 -0
- alysis_code/cli_impl/commands/skills.py +404 -0
- alysis_code/cli_impl/commands/startup.py +946 -0
- alysis_code/cli_impl/commands/tools.py +335 -0
- alysis_code/cli_impl/commands/update.py +364 -0
- alysis_code/cli_impl/commands/welcome.py +972 -0
- alysis_code/cli_impl/config_menu.py +3882 -0
- alysis_code/cli_impl/forge.py +4509 -0
- alysis_code/cli_impl/forge_recovery.py +485 -0
- alysis_code/cli_impl/setup_wizard.py +2409 -0
- alysis_code/cli_impl/tui/__init__.py +58 -0
- alysis_code/cli_impl/tui/app.py +4551 -0
- alysis_code/cli_impl/tui/config.py +32 -0
- alysis_code/cli_impl/tui/config_flow.py +2754 -0
- alysis_code/cli_impl/tui/config_overlay.py +566 -0
- alysis_code/cli_impl/tui/content.py +78 -0
- alysis_code/cli_impl/tui/footer.py +218 -0
- alysis_code/cli_impl/tui/forge_status.py +136 -0
- alysis_code/cli_impl/tui/markdown.py +244 -0
- alysis_code/cli_impl/tui/owl.py +109 -0
- alysis_code/cli_impl/tui/plan_meta.py +477 -0
- alysis_code/cli_impl/tui/setup_app.py +519 -0
- alysis_code/cli_impl/tui/setup_flow.py +1622 -0
- alysis_code/cli_impl/tui/state.py +101 -0
- alysis_code/cli_impl/tui/subagent_identity.py +66 -0
- alysis_code/cli_impl/tui/subagent_panel.py +186 -0
- alysis_code/cli_impl/tui/surface.py +796 -0
- alysis_code/cli_impl/tui/transcript.py +514 -0
- alysis_code/cli_impl/tui/update_prompt.py +79 -0
- alysis_code/cli_impl/tui/workspace_guard.py +384 -0
- alysis_code/clipboard.py +172 -0
- alysis_code/code_review.py +1211 -0
- alysis_code/compaction/__init__.py +28 -0
- alysis_code/compaction/conversation_compactor.py +2932 -0
- alysis_code/compaction/importance.py +177 -0
- alysis_code/compaction/settings.py +297 -0
- alysis_code/compaction/tool_output_offload.py +447 -0
- alysis_code/config.py +3509 -0
- alysis_code/conflict_auto_resolver.py +895 -0
- alysis_code/context/__init__.py +1 -0
- alysis_code/context/tool_schema_budgeter.py +220 -0
- alysis_code/crash_diagnostics.py +282 -0
- alysis_code/custom_tools/README.md +34 -0
- alysis_code/custom_tools/__init__.py +43 -0
- alysis_code/custom_tools/discovery.py +903 -0
- alysis_code/custom_tools/runtime.py +1516 -0
- alysis_code/custom_tools/session.py +227 -0
- alysis_code/custom_tools/trust.py +232 -0
- alysis_code/diff_paths.py +113 -0
- alysis_code/direction_change.py +293 -0
- alysis_code/dispatch_timing.py +306 -0
- alysis_code/durable_service_manager.py +1236 -0
- alysis_code/edit_discipline.py +659 -0
- alysis_code/error_text.py +73 -0
- alysis_code/execution_budget.py +411 -0
- alysis_code/execution_context.py +915 -0
- alysis_code/execution_deadline.py +1065 -0
- alysis_code/execution_shared.py +1904 -0
- alysis_code/extensions/README.md +30 -0
- alysis_code/extensions/__init__.py +93 -0
- alysis_code/extensions/activation.py +138 -0
- alysis_code/extensions/install.py +1436 -0
- alysis_code/extensions/manifest.py +487 -0
- alysis_code/extensions/models.py +74 -0
- alysis_code/extensions/paths.py +56 -0
- alysis_code/extensions/registry.json +4 -0
- alysis_code/extensions/registry.py +52 -0
- alysis_code/extensions/state.py +83 -0
- alysis_code/extensions/workspace_trust.py +101 -0
- alysis_code/failed_task_evidence.py +369 -0
- alysis_code/failure_category.py +315 -0
- alysis_code/feedback_report.py +1647 -0
- alysis_code/file_classification.py +485 -0
- alysis_code/forge.py +2064 -0
- alysis_code/forge_completion.py +362 -0
- alysis_code/forge_events.py +475 -0
- alysis_code/frontmatter_utils.py +95 -0
- alysis_code/git_evidence.py +1181 -0
- alysis_code/git_ops.py +560 -0
- alysis_code/git_safe.py +62 -0
- alysis_code/git_worktrees.py +190 -0
- alysis_code/hooks/README.md +33 -0
- alysis_code/hooks/__init__.py +67 -0
- alysis_code/hooks/audit.py +171 -0
- alysis_code/hooks/config.py +225 -0
- alysis_code/hooks/dispatcher.py +1110 -0
- alysis_code/hooks/models.py +447 -0
- alysis_code/hooks/trust.py +202 -0
- alysis_code/host_actions.py +543 -0
- alysis_code/host_browser.py +103 -0
- alysis_code/ide/__init__.py +5 -0
- alysis_code/ide/activity_events.py +399 -0
- alysis_code/ide/approvals.py +337 -0
- alysis_code/ide/artifacts.py +153 -0
- alysis_code/ide/browser_egress_proxy.py +1076 -0
- alysis_code/ide/cdp_websocket_transport.py +1192 -0
- alysis_code/ide/change_ledger.py +1721 -0
- alysis_code/ide/context_blocks.py +979 -0
- alysis_code/ide/event_stream.py +531 -0
- alysis_code/ide/forge_protocol.py +3112 -0
- alysis_code/ide/forge_request_ledger.py +737 -0
- alysis_code/ide/health.py +965 -0
- alysis_code/ide/managed_browser.py +2251 -0
- alysis_code/ide/management_protocol.py +3414 -0
- alysis_code/ide/mcp_oauth_coordinator.py +744 -0
- alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
- alysis_code/ide/prompt_queue.py +1070 -0
- alysis_code/ide/protocol.py +191 -0
- alysis_code/ide/resumable_swarm.py +1543 -0
- alysis_code/ide/session_search.py +295 -0
- alysis_code/ide/stdio_bridge.py +9935 -0
- alysis_code/ide/structured_state.py +1579 -0
- alysis_code/ide/swarm_protocol.py +816 -0
- alysis_code/integration_gate.py +506 -0
- alysis_code/interactive_input_guard.py +39 -0
- alysis_code/interactive_plan_mode.py +26 -0
- alysis_code/internal_artifacts.py +179 -0
- alysis_code/knowledge_base.py +1409 -0
- alysis_code/knowledge_capture.py +1190 -0
- alysis_code/knowledge_librarian.py +605 -0
- alysis_code/language_policy.py +34 -0
- alysis_code/litellm_static_provider.py +535 -0
- alysis_code/llm/__init__.py +1 -0
- alysis_code/llm/anthropic_messages.py +2288 -0
- alysis_code/llm/base.py +71 -0
- alysis_code/llm/cache_capabilities.py +985 -0
- alysis_code/llm/cache_control_blocks.py +244 -0
- alysis_code/llm/cache_policy.py +388 -0
- alysis_code/llm/factory.py +373 -0
- alysis_code/llm/gemini_generate_content.py +2652 -0
- alysis_code/llm/gemini_interactions.py +739 -0
- alysis_code/llm/metadata.py +450 -0
- alysis_code/llm/openai_compat.py +2947 -0
- alysis_code/llm/openai_responses.py +2604 -0
- alysis_code/llm/protocols.py +609 -0
- alysis_code/llm/provider_limits.py +525 -0
- alysis_code/llm/request_plan.py +389 -0
- alysis_code/llm/request_shape.py +238 -0
- alysis_code/llm/streaming.py +108 -0
- alysis_code/llm/temperature_compat.py +78 -0
- alysis_code/llm/types.py +195 -0
- alysis_code/llm/usage_normalization.py +222 -0
- alysis_code/llm_error_display.py +315 -0
- alysis_code/logging_redaction.py +326 -0
- alysis_code/managed_host_deadline.py +196 -0
- alysis_code/mcp/README.md +33 -0
- alysis_code/mcp/__init__.py +24 -0
- alysis_code/mcp/client.py +1137 -0
- alysis_code/mcp/config.py +597 -0
- alysis_code/mcp/errors.py +113 -0
- alysis_code/mcp/forge_scope.py +154 -0
- alysis_code/mcp/jsonrpc.py +214 -0
- alysis_code/mcp/manager.py +2308 -0
- alysis_code/mcp/models.py +666 -0
- alysis_code/mcp/oauth.py +972 -0
- alysis_code/mcp/oauth_runtime.py +310 -0
- alysis_code/mcp/oauth_store.py +276 -0
- alysis_code/mcp/prompts.py +329 -0
- alysis_code/mcp/resources.py +295 -0
- alysis_code/mcp/roots.py +106 -0
- alysis_code/mcp/server_requests.py +75 -0
- alysis_code/mcp/token_store.py +859 -0
- alysis_code/mcp/transport_http.py +1338 -0
- alysis_code/mcp/transport_stdio.py +1267 -0
- alysis_code/mcp/untrusted_content.py +119 -0
- alysis_code/merge_conflict_reviewer.py +729 -0
- alysis_code/model_catalog/__init__.py +1 -0
- alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
- alysis_code/model_metadata_policy.py +223 -0
- alysis_code/model_metadata_utils.py +103 -0
- alysis_code/model_registry.py +1420 -0
- alysis_code/model_router.py +147 -0
- alysis_code/permission_policy.py +1016 -0
- alysis_code/personas.py +451 -0
- alysis_code/pipeline_facts.py +233 -0
- alysis_code/plan_assistant.py +4763 -0
- alysis_code/plan_mode.py +393 -0
- alysis_code/plan_reconciliation.py +1228 -0
- alysis_code/plan_repair.py +652 -0
- alysis_code/plan_validation.py +1099 -0
- alysis_code/planning_constraints.py +904 -0
- alysis_code/policy.py +95 -0
- alysis_code/preview_server.py +457 -0
- alysis_code/process_reaping.py +566 -0
- alysis_code/profile_presets.py +1834 -0
- alysis_code/profiles.py +666 -0
- alysis_code/provider_auth/__init__.py +29 -0
- alysis_code/provider_auth/base.py +99 -0
- alysis_code/provider_auth/openai_codex.py +951 -0
- alysis_code/provider_auth/registry.py +76 -0
- alysis_code/provider_auth/store.py +125 -0
- alysis_code/provider_diagnostics.py +1209 -0
- alysis_code/provider_model_catalog.py +685 -0
- alysis_code/provider_telemetry.py +1699 -0
- alysis_code/provider_url.py +75 -0
- alysis_code/reasoning_contracts.py +911 -0
- alysis_code/remote_sync.py +350 -0
- alysis_code/replanning.py +1195 -0
- alysis_code/repo_scan.py +1152 -0
- alysis_code/request_estimation.py +296 -0
- alysis_code/review_gate.py +617 -0
- alysis_code/run_lock.py +1141 -0
- alysis_code/run_outcome.py +58 -0
- alysis_code/run_provenance.py +774 -0
- alysis_code/run_state.py +445 -0
- alysis_code/runtime_artifacts.py +116 -0
- alysis_code/runtime_context_features.py +78 -0
- alysis_code/runtime_kind.py +52 -0
- alysis_code/safety/__init__.py +11 -0
- alysis_code/safety/mcp_sanitize.py +29 -0
- alysis_code/safety/safe_http.py +297 -0
- alysis_code/safety/subagent_report.py +184 -0
- alysis_code/sandbox_doctor.py +682 -0
- alysis_code/sandbox_runner.py +1025 -0
- alysis_code/sandbox_settings.py +423 -0
- alysis_code/serialized_paths.py +355 -0
- alysis_code/server/__init__.py +3 -0
- alysis_code/server/app.py +367 -0
- alysis_code/server/auth.py +34 -0
- alysis_code/server/job_config.py +30 -0
- alysis_code/server/settings.py +215 -0
- alysis_code/server/store.py +193 -0
- alysis_code/server/worker_runner.py +657 -0
- alysis_code/service_persistence.py +355 -0
- alysis_code/session_artifacts.py +108 -0
- alysis_code/session_metrics.py +331 -0
- alysis_code/session_store.py +624 -0
- alysis_code/skills/README.md +34 -0
- alysis_code/skills/__init__.py +104 -0
- alysis_code/skills/conventions.py +84 -0
- alysis_code/skills/discovery.py +176 -0
- alysis_code/skills/eval_models.py +232 -0
- alysis_code/skills/eval_runner.py +372 -0
- alysis_code/skills/evals.py +1344 -0
- alysis_code/skills/install.py +293 -0
- alysis_code/skills/loader.py +118 -0
- alysis_code/skills/matching.py +103 -0
- alysis_code/skills/models.py +71 -0
- alysis_code/skills/paths.py +56 -0
- alysis_code/skills/prompting.py +500 -0
- alysis_code/skills/scaffold.py +142 -0
- alysis_code/skills/state.py +441 -0
- alysis_code/skills/transactions.py +125 -0
- alysis_code/skills/validation.py +304 -0
- alysis_code/step_budget.py +238 -0
- alysis_code/subagent_labels.py +49 -0
- alysis_code/subagents.py +1072 -0
- alysis_code/surface/__init__.py +80 -0
- alysis_code/surface/base.py +305 -0
- alysis_code/surface/console.py +387 -0
- alysis_code/surface/events.py +372 -0
- alysis_code/surface/hidden_surface.py +529 -0
- alysis_code/surface/noop_surface.py +219 -0
- alysis_code/surface/rich_surface.py +1555 -0
- alysis_code/surface/styles.py +67 -0
- alysis_code/surface/theme.py +455 -0
- alysis_code/surface/types.py +100 -0
- alysis_code/swarm_backend.py +926 -0
- alysis_code/swarm_orchestrator.py +4020 -0
- alysis_code/swarm_scheduler.py +441 -0
- alysis_code/swarm_trace.py +429 -0
- alysis_code/swarm_worker.py +2119 -0
- alysis_code/swarm_write_guard.py +348 -0
- alysis_code/task_dependencies.py +170 -0
- alysis_code/task_readiness.py +992 -0
- alysis_code/task_scope.py +2148 -0
- alysis_code/terminal_manager.py +762 -0
- alysis_code/terminal_ownership.py +460 -0
- alysis_code/text_normalization.py +30 -0
- alysis_code/token_budget.py +97 -0
- alysis_code/tools/README.md +34 -0
- alysis_code/tools/__init__.py +1 -0
- alysis_code/tools/artifacts.py +127 -0
- alysis_code/tools/availability.py +188 -0
- alysis_code/tools/fs.py +1456 -0
- alysis_code/tools/git.py +461 -0
- alysis_code/tools/history.py +229 -0
- alysis_code/tools/http_timeout.py +78 -0
- alysis_code/tools/image_generation.py +552 -0
- alysis_code/tools/registry.py +2936 -0
- alysis_code/tools/repo_map.py +476 -0
- alysis_code/tools/search.py +563 -0
- alysis_code/tools/shell.py +135 -0
- alysis_code/tools/symbols.py +1350 -0
- alysis_code/tools/test_discovery.py +643 -0
- alysis_code/tools/web.py +482 -0
- alysis_code/tools/web_search.py +2012 -0
- alysis_code/tools/web_search_dashscope.py +557 -0
- alysis_code/tools/web_search_ddgs.py +221 -0
- alysis_code/tools/web_search_provider_adapters.py +1429 -0
- alysis_code/tools/web_search_tavily.py +194 -0
- alysis_code/updates.py +933 -0
- alysis_code/usage_tracker.py +1990 -0
- alysis_code/verification_command_analysis.py +1004 -0
- alysis_code/verification_contract.py +574 -0
- alysis_code/verification_failure_summary.py +273 -0
- alysis_code/verification_repair.py +385 -0
- alysis_code/verify_gate.py +3129 -0
- alysis_code/web_research.py +1872 -0
- alysis_code/web_search_adapters.py +66 -0
- alysis_code/web_search_policy.py +27 -0
- alysis_code/workspace_binding.py +389 -0
- alysis_code/workspace_binding_ui.py +408 -0
- alysis_code/workspace_context.py +273 -0
- alysis_code/workspace_isolation.py +138 -0
- alysis_code/workspace_provisioning.py +455 -0
- alysis_code-0.13.0.dist-info/METADATA +507 -0
- alysis_code-0.13.0.dist-info/RECORD +458 -0
- alysis_code-0.13.0.dist-info/WHEEL +4 -0
- alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
- alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
- alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"""Getting a workspace un-stuck after an execution died: `forge unlock`, `forge resume`.
|
|
2
|
+
|
|
3
|
+
An interrupted run leaves two kinds of residue, and each needs its own answer.
|
|
4
|
+
|
|
5
|
+
*The lock* outlives the process that took it. ``forge unlock`` reads it, explains
|
|
6
|
+
the staleness verdict in words, and clears it when -- and only when -- the owner is
|
|
7
|
+
provably gone. ``--force`` exists for the genuinely ambiguous case (another host,
|
|
8
|
+
an unprobeable pid), and says plainly that the operator, not the tool, is the one
|
|
9
|
+
asserting the owner is dead.
|
|
10
|
+
|
|
11
|
+
*The run pointer* outlives it too. ``forge resume`` picks a run back up from where
|
|
12
|
+
it stopped: it re-checks the plan against the fingerprint recorded when the run
|
|
13
|
+
started, and if the plan moved in the meantime it says exactly what moved and
|
|
14
|
+
stops for re-approval rather than either silently continuing or silently throwing
|
|
15
|
+
the approval away.
|
|
16
|
+
|
|
17
|
+
Both commands are pure recovery. Neither invents work: unlock only deletes lock
|
|
18
|
+
files, and resume delegates the actual execution to ``forge run`` once it has
|
|
19
|
+
established that resuming is safe.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
import typer
|
|
29
|
+
|
|
30
|
+
from ..forge import (
|
|
31
|
+
ForgeError,
|
|
32
|
+
current_run_pointer_path,
|
|
33
|
+
load_current_run_paths,
|
|
34
|
+
load_plan,
|
|
35
|
+
read_current_run_pointer,
|
|
36
|
+
reconcile_current_run_status,
|
|
37
|
+
save_plan,
|
|
38
|
+
set_current_run_status,
|
|
39
|
+
)
|
|
40
|
+
from ..forge_events import (
|
|
41
|
+
EXIT_ERROR,
|
|
42
|
+
EXIT_NOT_ACCEPTED,
|
|
43
|
+
EXIT_OK,
|
|
44
|
+
ForgeEventEmitter,
|
|
45
|
+
)
|
|
46
|
+
from ..run_lock import (
|
|
47
|
+
STALENESS_STALE,
|
|
48
|
+
clear_run_mutation_lock,
|
|
49
|
+
describe_run_mutation_lock,
|
|
50
|
+
)
|
|
51
|
+
from ..run_state import (
|
|
52
|
+
RESUMABLE_RUN_STATUSES,
|
|
53
|
+
RUN_STATUS_APPROVED,
|
|
54
|
+
RUN_STATUS_COMPLETED,
|
|
55
|
+
RUN_STATUS_DRAFT,
|
|
56
|
+
RUN_STATUS_RUNNING,
|
|
57
|
+
compare_plan_fingerprints,
|
|
58
|
+
describe_run_status,
|
|
59
|
+
plan_fingerprint,
|
|
60
|
+
pointer_status,
|
|
61
|
+
)
|
|
62
|
+
from ..workspace_binding import (
|
|
63
|
+
WorkspaceAction,
|
|
64
|
+
WorkspaceBindingError,
|
|
65
|
+
ensure_workspace_policy,
|
|
66
|
+
resolve_workspace_binding,
|
|
67
|
+
)
|
|
68
|
+
from .commands._shared import _console
|
|
69
|
+
|
|
70
|
+
_WORKSPACE_LOCK_DIR_NAME = "workspace_execution"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _events_or_null(events: Any) -> ForgeEventEmitter:
|
|
74
|
+
if isinstance(events, ForgeEventEmitter):
|
|
75
|
+
return events
|
|
76
|
+
return ForgeEventEmitter(command="forge", enabled=False)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _lock_targets(path: Path) -> tuple[Path, list[tuple[str, Path]], Any]:
|
|
80
|
+
"""Locate the workspace lock and, when resolvable, the current run's lock.
|
|
81
|
+
|
|
82
|
+
The workspace lock is found from the path alone, on purpose: a workspace whose
|
|
83
|
+
``current_run.json`` is missing or corrupt is exactly the workspace most likely
|
|
84
|
+
to be stuck, and it must still be unlockable.
|
|
85
|
+
"""
|
|
86
|
+
binding = resolve_workspace_binding(path, create_if_missing=False, source="explicit_path")
|
|
87
|
+
root = binding.workspace_context.workspace_root
|
|
88
|
+
paths = None
|
|
89
|
+
try:
|
|
90
|
+
paths = load_current_run_paths(binding.workspace_context.focus_path)
|
|
91
|
+
except ForgeError:
|
|
92
|
+
paths = None
|
|
93
|
+
runtime_dir = paths.runtime_dir if paths is not None else root / ".alysis"
|
|
94
|
+
# Workspace lock first: it is the one that blocks every command, and the one that
|
|
95
|
+
# still exists when there is no resolvable run to report a run lock for.
|
|
96
|
+
targets: list[tuple[str, Path]] = [("workspace", runtime_dir / _WORKSPACE_LOCK_DIR_NAME)]
|
|
97
|
+
if paths is not None:
|
|
98
|
+
targets.append((f"run {paths.run_id}", paths.run_dir))
|
|
99
|
+
return root, targets, paths
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _print_lock_report(console: Any, report: dict[str, Any]) -> None:
|
|
103
|
+
staleness = report.get("staleness") or {}
|
|
104
|
+
lock = report.get("lock") or {}
|
|
105
|
+
verdict = str(staleness.get("verdict") or "unknown")
|
|
106
|
+
console.print(f"[bold]{report.get('label')}[/bold] lock: {verdict}")
|
|
107
|
+
console.print(f" reason: {staleness.get('reason') or '(none recorded)'}")
|
|
108
|
+
for label, key in (
|
|
109
|
+
("owner", "owner_id"),
|
|
110
|
+
("host", "hostname"),
|
|
111
|
+
("pid", "pid"),
|
|
112
|
+
("mode", "mode"),
|
|
113
|
+
):
|
|
114
|
+
# The verdict carries these when it could parse them; the raw lock is the
|
|
115
|
+
# fallback for a lock too malformed to assess but still worth describing.
|
|
116
|
+
value = staleness.get(key) or lock.get(key)
|
|
117
|
+
if value not in (None, ""):
|
|
118
|
+
console.print(f" {label}: {value}")
|
|
119
|
+
for label, key in (("age", "age_s"), ("heartbeat age", "heartbeat_age_s")):
|
|
120
|
+
seconds = staleness.get(key)
|
|
121
|
+
if isinstance(seconds, (int, float)):
|
|
122
|
+
console.print(f" {label}: {_format_seconds(float(seconds))}")
|
|
123
|
+
console.print(f" file: {report.get('lock_path')}")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _format_seconds(seconds: float) -> str:
|
|
127
|
+
if seconds < 90:
|
|
128
|
+
return f"{seconds:.0f}s"
|
|
129
|
+
if seconds < 5400:
|
|
130
|
+
return f"{seconds / 60:.0f}m"
|
|
131
|
+
if seconds < 172800:
|
|
132
|
+
return f"{seconds / 3600:.1f}h"
|
|
133
|
+
return f"{seconds / 86400:.1f}d"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def forge_unlock(
|
|
137
|
+
path: Path = typer.Option(Path("."), "--path"),
|
|
138
|
+
force: bool = typer.Option(False, "--force"),
|
|
139
|
+
events: Any = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Inspect the workspace's Forge locks and clear the ones that are safe to clear."""
|
|
142
|
+
events = _events_or_null(events)
|
|
143
|
+
console = _console()
|
|
144
|
+
forced = force is True
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
root, targets, paths = _lock_targets(path)
|
|
148
|
+
except (ForgeError, WorkspaceBindingError) as e:
|
|
149
|
+
console.print(f"[red]Forge error:[/red] {e}")
|
|
150
|
+
events.error(message=str(e), kind="forge_error", exit_code=EXIT_ERROR)
|
|
151
|
+
raise typer.Exit(code=EXIT_ERROR) from e
|
|
152
|
+
|
|
153
|
+
if paths is not None:
|
|
154
|
+
events.set_run_id(paths.run_id)
|
|
155
|
+
|
|
156
|
+
console.rule("[bold]forge unlock[/bold]")
|
|
157
|
+
console.print(f"Workspace: {os.fspath(root)}")
|
|
158
|
+
|
|
159
|
+
reports: list[dict[str, Any]] = []
|
|
160
|
+
blocked: list[dict[str, Any]] = []
|
|
161
|
+
cleared: list[dict[str, Any]] = []
|
|
162
|
+
|
|
163
|
+
for label, run_dir in targets:
|
|
164
|
+
present = describe_run_mutation_lock(run_dir, label=label)
|
|
165
|
+
if present is None:
|
|
166
|
+
console.print(f"[bold]{label}[/bold] lock: none")
|
|
167
|
+
reports.append({"label": label, "present": False, "cleared": False})
|
|
168
|
+
continue
|
|
169
|
+
_print_lock_report(console, present)
|
|
170
|
+
verdict = str((present.get("staleness") or {}).get("verdict") or "")
|
|
171
|
+
if verdict != STALENESS_STALE and not forced:
|
|
172
|
+
console.print(
|
|
173
|
+
" [yellow]kept:[/yellow] this lock is not provably stale. Wait for the "
|
|
174
|
+
"owner to finish, or re-run with --force if you are certain that process "
|
|
175
|
+
"is gone."
|
|
176
|
+
)
|
|
177
|
+
blocked.append(present)
|
|
178
|
+
reports.append({**present, "present": True, "cleared": False, "forced": False})
|
|
179
|
+
continue
|
|
180
|
+
result = clear_run_mutation_lock(run_dir, force=forced)
|
|
181
|
+
if result.get("cleared"):
|
|
182
|
+
if result.get("forced"):
|
|
183
|
+
console.print(
|
|
184
|
+
" [yellow]force-cleared:[/yellow] the owner was not provably gone. "
|
|
185
|
+
"If it is still alive, two executions can now mutate this workspace "
|
|
186
|
+
"at once."
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
console.print(" [green]cleared:[/green] owner was provably gone.")
|
|
190
|
+
cleared.append(result)
|
|
191
|
+
reports.append(result)
|
|
192
|
+
|
|
193
|
+
# A cleared lock is only half the recovery: the pointer may still claim `running`.
|
|
194
|
+
status_after = None
|
|
195
|
+
if root is not None:
|
|
196
|
+
try:
|
|
197
|
+
reconcile_current_run_status(root)
|
|
198
|
+
status_after = pointer_status(read_current_run_pointer(root))
|
|
199
|
+
except Exception: # noqa: BLE001 - reporting only
|
|
200
|
+
status_after = None
|
|
201
|
+
|
|
202
|
+
if status_after is not None:
|
|
203
|
+
console.print(f"Run status: {status_after} ({describe_run_status(status_after)})")
|
|
204
|
+
if status_after in RESUMABLE_RUN_STATUSES:
|
|
205
|
+
console.print("Continue this run with: alysis forge resume --path .")
|
|
206
|
+
|
|
207
|
+
exit_code = EXIT_NOT_ACCEPTED if blocked else EXIT_OK
|
|
208
|
+
if not cleared and not blocked:
|
|
209
|
+
console.print("Nothing to clear.")
|
|
210
|
+
events.run_completed(
|
|
211
|
+
ok=exit_code == EXIT_OK,
|
|
212
|
+
exit_code=exit_code,
|
|
213
|
+
data={
|
|
214
|
+
"workspace_root": os.fspath(root),
|
|
215
|
+
"forced": forced,
|
|
216
|
+
"locks": reports,
|
|
217
|
+
"cleared_count": len(cleared),
|
|
218
|
+
"blocked_count": len(blocked),
|
|
219
|
+
"run_status": status_after,
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
raise typer.Exit(code=exit_code)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _in_flight_task_ids(plan: dict[str, Any]) -> list[str]:
|
|
226
|
+
"""Task ids the dead process left mid-execution."""
|
|
227
|
+
return sorted(
|
|
228
|
+
str(task.get("id") or "")
|
|
229
|
+
for task in plan.get("tasks") or []
|
|
230
|
+
if isinstance(task, dict)
|
|
231
|
+
and str(task.get("status") or "").strip().lower() == "in_progress"
|
|
232
|
+
and str(task.get("id") or "")
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _reset_in_flight_tasks(paths: Any, plan: dict[str, Any]) -> list[str]:
|
|
237
|
+
"""Move tasks the dead process left ``in_progress`` back to a runnable status.
|
|
238
|
+
|
|
239
|
+
``in_progress`` is not a runnable status -- by design, so a live run cannot pick
|
|
240
|
+
up its own task twice. That also means a killed run's task would be skipped
|
|
241
|
+
forever, so resume rewrites it to ``interrupted``, which *is* runnable and which
|
|
242
|
+
records what happened rather than pretending the task was never started.
|
|
243
|
+
"""
|
|
244
|
+
reset: list[str] = []
|
|
245
|
+
for task in plan.get("tasks") or []:
|
|
246
|
+
if not isinstance(task, dict):
|
|
247
|
+
continue
|
|
248
|
+
if str(task.get("status") or "").strip().lower() != "in_progress":
|
|
249
|
+
continue
|
|
250
|
+
# The *task* status vocabulary, which happens to share this word with the run
|
|
251
|
+
# lifecycle enum. `swarm_scheduler._runnable_status` treats it as runnable.
|
|
252
|
+
task["status"] = "interrupted"
|
|
253
|
+
reset.append(str(task.get("id") or ""))
|
|
254
|
+
if reset:
|
|
255
|
+
save_plan(paths, plan)
|
|
256
|
+
return reset
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def forge_resume(
|
|
260
|
+
cli_mod: Any,
|
|
261
|
+
*,
|
|
262
|
+
path: Path,
|
|
263
|
+
reapprove: bool = False,
|
|
264
|
+
dry_run: bool = False,
|
|
265
|
+
events: Any = None,
|
|
266
|
+
**run_kwargs: Any,
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Continue an interrupted run from its last incomplete task."""
|
|
269
|
+
events = _events_or_null(events)
|
|
270
|
+
console = _console()
|
|
271
|
+
reapprove_requested = reapprove is True
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
binding = resolve_workspace_binding(
|
|
275
|
+
path,
|
|
276
|
+
create_if_missing=False,
|
|
277
|
+
allow_broad_workspace=bool(run_kwargs.get("allow_broad_workspace")),
|
|
278
|
+
source="explicit_path",
|
|
279
|
+
)
|
|
280
|
+
ensure_workspace_policy(
|
|
281
|
+
binding,
|
|
282
|
+
action=WorkspaceAction.FORGE_RUN,
|
|
283
|
+
allow_broad_workspace=bool(run_kwargs.get("allow_broad_workspace")),
|
|
284
|
+
)
|
|
285
|
+
# Loading the pointer is what reconciles a crash leftover, so the status read
|
|
286
|
+
# below is the post-crash truth rather than the dead process's last claim.
|
|
287
|
+
paths = load_current_run_paths(binding.workspace_context.focus_path)
|
|
288
|
+
plan = load_plan(paths)
|
|
289
|
+
except (ForgeError, WorkspaceBindingError) as e:
|
|
290
|
+
console.print(f"[red]Forge error:[/red] {e}")
|
|
291
|
+
events.error(message=str(e), kind="forge_error", exit_code=EXIT_ERROR)
|
|
292
|
+
raise typer.Exit(code=EXIT_ERROR) from e
|
|
293
|
+
|
|
294
|
+
events.set_run_id(paths.run_id)
|
|
295
|
+
pointer = read_current_run_pointer(paths.root)
|
|
296
|
+
status = pointer_status(pointer)
|
|
297
|
+
|
|
298
|
+
console.rule("[bold]forge resume[/bold]")
|
|
299
|
+
console.print(f"Run ID: {paths.run_id}")
|
|
300
|
+
console.print(f"Status: {status} ({describe_run_status(status)})")
|
|
301
|
+
reason = str((pointer or {}).get("status_reason") or "").strip()
|
|
302
|
+
if reason:
|
|
303
|
+
console.print(f"[dim]{reason}[/dim]")
|
|
304
|
+
|
|
305
|
+
blocker = _resume_blocker(status)
|
|
306
|
+
if blocker is not None:
|
|
307
|
+
console.print(f"[red]Cannot resume:[/red] {blocker}")
|
|
308
|
+
events.error(
|
|
309
|
+
message=blocker,
|
|
310
|
+
kind="forge_error",
|
|
311
|
+
exit_code=EXIT_ERROR,
|
|
312
|
+
data={"run_status": status, "run_id": paths.run_id},
|
|
313
|
+
)
|
|
314
|
+
raise typer.Exit(code=EXIT_ERROR)
|
|
315
|
+
|
|
316
|
+
# `--dry-run` reports and exits, so from here on nothing may be written until it
|
|
317
|
+
# has had its say -- including the re-approval and the in-flight task re-arming.
|
|
318
|
+
dry = dry_run is True
|
|
319
|
+
|
|
320
|
+
current_fingerprint = plan_fingerprint(plan)
|
|
321
|
+
drift = compare_plan_fingerprints((pointer or {}).get("plan_fingerprint"), current_fingerprint)
|
|
322
|
+
if drift.changed:
|
|
323
|
+
console.print("[yellow]The plan changed since this run was approved:[/yellow]")
|
|
324
|
+
for line in drift.reasons:
|
|
325
|
+
console.print(f"- {line}")
|
|
326
|
+
if not reapprove_requested:
|
|
327
|
+
message = (
|
|
328
|
+
"the plan drifted since this run started, so resuming would execute work "
|
|
329
|
+
"that was never approved. Review the changes above, then re-run with "
|
|
330
|
+
"--reapprove to accept the current plan and continue."
|
|
331
|
+
)
|
|
332
|
+
console.print(f"[red]Cannot resume:[/red] {message}")
|
|
333
|
+
# Deliberately not demoted to draft: the operator has not rejected this
|
|
334
|
+
# plan, they have not looked at it yet. Throwing the approval away here
|
|
335
|
+
# would make "I looked and it is fine" cost a full re-plan.
|
|
336
|
+
events.error(
|
|
337
|
+
message=message,
|
|
338
|
+
kind="plan_drift",
|
|
339
|
+
exit_code=EXIT_NOT_ACCEPTED,
|
|
340
|
+
data={
|
|
341
|
+
"run_status": status,
|
|
342
|
+
"run_id": paths.run_id,
|
|
343
|
+
"drift": drift.to_json(),
|
|
344
|
+
},
|
|
345
|
+
)
|
|
346
|
+
raise typer.Exit(code=EXIT_NOT_ACCEPTED)
|
|
347
|
+
if dry:
|
|
348
|
+
console.print("Would re-approve the current plan and continue.")
|
|
349
|
+
else:
|
|
350
|
+
console.print("[green]Re-approved:[/green] continuing against the current plan.")
|
|
351
|
+
with_fingerprint = set_current_run_status(
|
|
352
|
+
paths,
|
|
353
|
+
RUN_STATUS_APPROVED,
|
|
354
|
+
reason="plan re-approved at resume after drift",
|
|
355
|
+
plan=plan,
|
|
356
|
+
)
|
|
357
|
+
if with_fingerprint is None:
|
|
358
|
+
console.print(
|
|
359
|
+
"[yellow]Note:[/yellow] could not update "
|
|
360
|
+
f"{current_run_pointer_path(paths.root)}; the run will still resume."
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
in_flight = _in_flight_task_ids(plan)
|
|
364
|
+
if in_flight:
|
|
365
|
+
verb = "would be re-armed for retry" if dry else "now retryable"
|
|
366
|
+
console.print(
|
|
367
|
+
f"Tasks left mid-flight by the interrupted run, {verb}: " + ", ".join(in_flight)
|
|
368
|
+
)
|
|
369
|
+
if not dry:
|
|
370
|
+
_reset_in_flight_tasks(paths, plan)
|
|
371
|
+
|
|
372
|
+
# Same definition of "outstanding" the sequential runner reports, so resume and
|
|
373
|
+
# `forge run`'s summary can never disagree about what is left.
|
|
374
|
+
from .forge import _unfinished_task_ids
|
|
375
|
+
|
|
376
|
+
remaining = _unfinished_task_ids(plan)
|
|
377
|
+
if not remaining:
|
|
378
|
+
console.print("Nothing left to execute." + ("" if dry else " Marking the run completed."))
|
|
379
|
+
if not dry:
|
|
380
|
+
set_current_run_status(
|
|
381
|
+
paths,
|
|
382
|
+
RUN_STATUS_COMPLETED,
|
|
383
|
+
reason="resume found every task already in a terminal successful status",
|
|
384
|
+
)
|
|
385
|
+
events.run_completed(
|
|
386
|
+
ok=True,
|
|
387
|
+
exit_code=EXIT_OK,
|
|
388
|
+
data={
|
|
389
|
+
"run_id": paths.run_id,
|
|
390
|
+
"resumed": False,
|
|
391
|
+
"remaining": [],
|
|
392
|
+
"dry_run": dry,
|
|
393
|
+
},
|
|
394
|
+
)
|
|
395
|
+
raise typer.Exit(code=EXIT_OK)
|
|
396
|
+
|
|
397
|
+
verb = "Would resume" if dry else "Resuming"
|
|
398
|
+
console.print(f"{verb} {len(remaining)} unfinished task(s): {', '.join(remaining)}")
|
|
399
|
+
if dry:
|
|
400
|
+
events.run_completed(
|
|
401
|
+
ok=True,
|
|
402
|
+
exit_code=EXIT_OK,
|
|
403
|
+
data={
|
|
404
|
+
"run_id": paths.run_id,
|
|
405
|
+
"resumed": False,
|
|
406
|
+
"remaining": remaining,
|
|
407
|
+
"dry_run": True,
|
|
408
|
+
},
|
|
409
|
+
)
|
|
410
|
+
raise typer.Exit(code=EXIT_OK)
|
|
411
|
+
|
|
412
|
+
from .forge import forge_run_impl
|
|
413
|
+
|
|
414
|
+
# `forge run` is the execution engine; resume's whole job was to establish that
|
|
415
|
+
# running it now is safe. Retry flags are on because the statuses resume exists
|
|
416
|
+
# to recover from -- interrupted, failed, changes_requested -- are precisely the
|
|
417
|
+
# ones a plain `forge run` skips.
|
|
418
|
+
return forge_run_impl(
|
|
419
|
+
cli_mod,
|
|
420
|
+
path,
|
|
421
|
+
run_kwargs.get("allow_broad_workspace", False),
|
|
422
|
+
run_kwargs.get("only"),
|
|
423
|
+
run_kwargs.get("max_tasks"),
|
|
424
|
+
run_kwargs.get("max_attempts"),
|
|
425
|
+
True,
|
|
426
|
+
True,
|
|
427
|
+
run_kwargs.get("keep_going", False),
|
|
428
|
+
False,
|
|
429
|
+
run_kwargs.get("scope", "strict"),
|
|
430
|
+
run_kwargs.get("verify", "warn"),
|
|
431
|
+
run_kwargs.get("verify_cmd"),
|
|
432
|
+
run_kwargs.get("verify_repair_attempts"),
|
|
433
|
+
run_kwargs.get("pr", True),
|
|
434
|
+
run_kwargs.get("review", False),
|
|
435
|
+
run_kwargs.get("base_branch"),
|
|
436
|
+
run_kwargs.get("keep_branch", False),
|
|
437
|
+
run_kwargs.get("auto_resolve_conflicts", False),
|
|
438
|
+
run_kwargs.get("mode"),
|
|
439
|
+
run_kwargs.get("model"),
|
|
440
|
+
run_kwargs.get("base_url"),
|
|
441
|
+
run_kwargs.get("temperature"),
|
|
442
|
+
run_kwargs.get("stream"),
|
|
443
|
+
run_kwargs.get("max_steps"),
|
|
444
|
+
run_kwargs.get("no_log", False),
|
|
445
|
+
run_kwargs.get("api_key_env"),
|
|
446
|
+
run_kwargs.get("api_key_stdin", False),
|
|
447
|
+
run_kwargs.get("api_key"),
|
|
448
|
+
run_kwargs.get("yes", False),
|
|
449
|
+
cli_ctx=run_kwargs.get("cli_ctx"),
|
|
450
|
+
events=events,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _resume_blocker(status: str) -> str | None:
|
|
455
|
+
if status in RESUMABLE_RUN_STATUSES:
|
|
456
|
+
return None
|
|
457
|
+
if status == RUN_STATUS_RUNNING:
|
|
458
|
+
return (
|
|
459
|
+
"this run is still marked running and something holds the workspace lock. "
|
|
460
|
+
"Wait for it to finish, or inspect the lock with `alysis forge unlock`."
|
|
461
|
+
)
|
|
462
|
+
if status == RUN_STATUS_COMPLETED:
|
|
463
|
+
return "this run already completed; start a new one with `alysis forge plan`."
|
|
464
|
+
if status == RUN_STATUS_APPROVED:
|
|
465
|
+
return (
|
|
466
|
+
"this run was never interrupted. Execute it with `alysis forge run` "
|
|
467
|
+
"instead of resuming."
|
|
468
|
+
)
|
|
469
|
+
if status == RUN_STATUS_DRAFT:
|
|
470
|
+
return (
|
|
471
|
+
"this run's plan is still a draft, so there is nothing to resume. Finish "
|
|
472
|
+
"planning first."
|
|
473
|
+
)
|
|
474
|
+
return f"unexpected run status: {status}"
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def forge_unlock_impl(*args: Any, **kwargs: Any) -> Any:
|
|
478
|
+
return forge_unlock(*args, **kwargs)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def forge_resume_impl(cli_mod: Any, **kwargs: Any) -> Any:
|
|
482
|
+
from .forge import _sync_cli_globals
|
|
483
|
+
|
|
484
|
+
_sync_cli_globals(cli_mod)
|
|
485
|
+
return forge_resume(cli_mod, **kwargs)
|