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,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ...surface.base import Surface
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _legacy_message_tool_events_required(surface: Surface | object) -> bool:
|
|
10
|
+
"""Whether a surface still needs the legacy half of dual event delivery."""
|
|
11
|
+
return not bool(getattr(surface, "canonical_message_tool_events", False))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _event_preview(value: Any, *, max_chars: int = 500) -> str:
|
|
15
|
+
try:
|
|
16
|
+
text = json.dumps(value, ensure_ascii=True, default=str)
|
|
17
|
+
except (TypeError, ValueError):
|
|
18
|
+
text = repr(value)
|
|
19
|
+
if len(text) <= max_chars:
|
|
20
|
+
return text
|
|
21
|
+
return text[: max_chars - 3].rstrip() + "..."
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _emit_message_delta_event(
|
|
25
|
+
surface: Surface | object,
|
|
26
|
+
text: str,
|
|
27
|
+
*,
|
|
28
|
+
worker_id: str | None = None,
|
|
29
|
+
role: str | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
if not text:
|
|
32
|
+
return
|
|
33
|
+
handler = getattr(surface, "emit_message_delta", None)
|
|
34
|
+
if callable(handler):
|
|
35
|
+
handler(text, worker_id=worker_id, role=role)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _emit_message_end_event(
|
|
39
|
+
surface: Surface | object,
|
|
40
|
+
text: str,
|
|
41
|
+
*,
|
|
42
|
+
worker_id: str | None = None,
|
|
43
|
+
role: str | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
handler = getattr(surface, "emit_message_end", None)
|
|
46
|
+
if callable(handler):
|
|
47
|
+
handler(text, worker_id=worker_id, role=role)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _emit_assistant_message_events(
|
|
51
|
+
surface: Surface | object,
|
|
52
|
+
text: str,
|
|
53
|
+
*,
|
|
54
|
+
streamed_text_emitted: bool = False,
|
|
55
|
+
worker_id: str | None = None,
|
|
56
|
+
role: str | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
if not streamed_text_emitted:
|
|
59
|
+
_emit_message_delta_event(surface, text, worker_id=worker_id, role=role)
|
|
60
|
+
_emit_message_end_event(surface, text, worker_id=worker_id, role=role)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _emit_tool_call_started_event(
|
|
64
|
+
surface: Surface | object,
|
|
65
|
+
*,
|
|
66
|
+
call_id: str,
|
|
67
|
+
name: str,
|
|
68
|
+
arguments: Any,
|
|
69
|
+
worker_id: str | None = None,
|
|
70
|
+
role: str | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
handler = getattr(surface, "emit_tool_call_started", None)
|
|
73
|
+
if callable(handler):
|
|
74
|
+
handler(
|
|
75
|
+
call_id,
|
|
76
|
+
name,
|
|
77
|
+
_event_preview(arguments),
|
|
78
|
+
worker_id=worker_id,
|
|
79
|
+
role=role,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _emit_tool_call_progress_event(
|
|
84
|
+
surface: Surface | object,
|
|
85
|
+
*,
|
|
86
|
+
call_id: str,
|
|
87
|
+
text: str,
|
|
88
|
+
worker_id: str | None = None,
|
|
89
|
+
role: str | None = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
handler = getattr(surface, "emit_tool_call_progress", None)
|
|
92
|
+
if callable(handler):
|
|
93
|
+
handler(call_id, text, worker_id=worker_id, role=role)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _emit_tool_call_completed_event(
|
|
97
|
+
surface: Surface | object,
|
|
98
|
+
*,
|
|
99
|
+
call_id: str,
|
|
100
|
+
success: bool,
|
|
101
|
+
result: Any,
|
|
102
|
+
worker_id: str | None = None,
|
|
103
|
+
role: str | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
handler = getattr(surface, "emit_tool_call_completed", None)
|
|
106
|
+
if callable(handler):
|
|
107
|
+
handler(
|
|
108
|
+
call_id,
|
|
109
|
+
success,
|
|
110
|
+
_event_preview(result),
|
|
111
|
+
worker_id=worker_id,
|
|
112
|
+
role=role,
|
|
113
|
+
)
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import shlex
|
|
6
|
+
from collections.abc import Collection
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ...text_normalization import normalize_for_matching
|
|
11
|
+
from ...tools.registry import get_builtin_tool_metadata
|
|
12
|
+
from ...verification_command_analysis import (
|
|
13
|
+
VerificationCommandEvidentiaryCapability,
|
|
14
|
+
analyze_verification_command,
|
|
15
|
+
)
|
|
16
|
+
from ..prompt_context import (
|
|
17
|
+
MAX_POST_EXPLORE_ANCHOR_PATHS,
|
|
18
|
+
_extract_repo_relative_paths_from_text,
|
|
19
|
+
_normalize_repo_relative_hint_path,
|
|
20
|
+
)
|
|
21
|
+
from ..verification import _runtime_message
|
|
22
|
+
|
|
23
|
+
MAX_RECENT_EXPLORATION_PATHS = 12
|
|
24
|
+
_EXPLORATION_FALLBACK_TOOL_NAMES = {
|
|
25
|
+
"fs_read",
|
|
26
|
+
"fs_read_lines",
|
|
27
|
+
"fs_list",
|
|
28
|
+
"symbol_search",
|
|
29
|
+
"repo_map",
|
|
30
|
+
"search_rg",
|
|
31
|
+
"history_search",
|
|
32
|
+
"git_status",
|
|
33
|
+
"git_diff",
|
|
34
|
+
"git_history",
|
|
35
|
+
}
|
|
36
|
+
_ACTION_PROGRESS_FALLBACK_TOOL_NAMES = {
|
|
37
|
+
"fs_edit",
|
|
38
|
+
"fs_move",
|
|
39
|
+
"fs_copy",
|
|
40
|
+
"fs_delete",
|
|
41
|
+
"fs_mkdir",
|
|
42
|
+
"fs_write",
|
|
43
|
+
"git_apply_patch",
|
|
44
|
+
"verify_run",
|
|
45
|
+
}
|
|
46
|
+
_ACTION_PROGRESS_TOOL_CATEGORIES = {"write", "verify", "subagent"}
|
|
47
|
+
_EXPLORATION_TOOL_CATEGORIES = {"read", "search", "history"}
|
|
48
|
+
_SUBAGENT_READ_CONTROL_TOOL_NAMES = {
|
|
49
|
+
"subagent_cancel",
|
|
50
|
+
"subagent_send",
|
|
51
|
+
"subagent_status",
|
|
52
|
+
}
|
|
53
|
+
_SUBAGENT_EFFECT_CLASSIFIED_TOOL_NAMES = {
|
|
54
|
+
"subagent_run",
|
|
55
|
+
"subagent_spawn",
|
|
56
|
+
"subagent_wait",
|
|
57
|
+
}
|
|
58
|
+
_FAILED_EDIT_STAGNATION_TOOL_NAMES = {"fs_edit", "git_apply_patch", "fs_write"}
|
|
59
|
+
_SHELL_TOOL_NAMES = {
|
|
60
|
+
"shell_run",
|
|
61
|
+
"shell_background",
|
|
62
|
+
"shell_service_start",
|
|
63
|
+
"shell_service_status",
|
|
64
|
+
"workspace_preview_start",
|
|
65
|
+
}
|
|
66
|
+
_SHELL_SERVICE_PROGRESS_TOOL_NAMES = {
|
|
67
|
+
"shell_background",
|
|
68
|
+
"shell_service_start",
|
|
69
|
+
"workspace_preview_start",
|
|
70
|
+
}
|
|
71
|
+
_UNEXECUTED_TOOL_CALL_MARKUP_MARKERS = (
|
|
72
|
+
"<tool_call",
|
|
73
|
+
"</tool_call",
|
|
74
|
+
"tool_calls",
|
|
75
|
+
"<function_calls",
|
|
76
|
+
"</function_calls",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _exploration_attempt_outcome(success_count: int, failed_count: int) -> str:
|
|
81
|
+
if success_count > 0 and failed_count > 0:
|
|
82
|
+
return "mixed"
|
|
83
|
+
if failed_count > 0:
|
|
84
|
+
return "failed"
|
|
85
|
+
if success_count > 0:
|
|
86
|
+
return "successful"
|
|
87
|
+
return "none"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _one_shot_progress_fingerprint(text: str) -> str:
|
|
91
|
+
normalized = normalize_for_matching(text)
|
|
92
|
+
return re.sub(r"[^\w ]+", "", normalized, flags=re.UNICODE)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _stagnation_detection_event_should_emit(detection_count: int, *, nudge_sent: bool) -> bool:
|
|
96
|
+
"""Whether the Nth stagnation detection of an episode should be recorded.
|
|
97
|
+
|
|
98
|
+
Once stagnation locks in, the detector fires on every subsequent step, so a
|
|
99
|
+
long stagnating turn used to log one near-identical event per step (50+ per
|
|
100
|
+
session). Detections that actually send a nudge are always recorded;
|
|
101
|
+
otherwise emission is spaced at power-of-two counts (1, 2, 4, 8, ...), so a
|
|
102
|
+
stagnating turn logs O(log n) events with unchanged prompt behavior — the
|
|
103
|
+
suppressed occurrences are summarized on the next emitted event.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
if nudge_sent:
|
|
107
|
+
return True
|
|
108
|
+
if detection_count <= 0:
|
|
109
|
+
return False
|
|
110
|
+
return detection_count & (detection_count - 1) == 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _tool_call_retry_key(name: str, arguments: dict[str, Any]) -> str:
|
|
114
|
+
try:
|
|
115
|
+
payload = json.dumps(arguments, ensure_ascii=True, sort_keys=True)
|
|
116
|
+
except TypeError:
|
|
117
|
+
payload = json.dumps(str(arguments), ensure_ascii=True)
|
|
118
|
+
return f"{name}:{payload}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _exploration_similarity_key(name: str, arguments: dict[str, Any]) -> str:
|
|
122
|
+
relevant_fields = (
|
|
123
|
+
"path",
|
|
124
|
+
"root_path",
|
|
125
|
+
"cmd",
|
|
126
|
+
"query",
|
|
127
|
+
"pattern",
|
|
128
|
+
"ref",
|
|
129
|
+
"commit",
|
|
130
|
+
)
|
|
131
|
+
parts = [name]
|
|
132
|
+
for key_name in relevant_fields:
|
|
133
|
+
value = arguments.get(key_name)
|
|
134
|
+
if value is None:
|
|
135
|
+
continue
|
|
136
|
+
parts.append(f"{key_name}={value}")
|
|
137
|
+
return "|".join(parts)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _edit_similarity_key(name: str, arguments: dict[str, Any]) -> str:
|
|
141
|
+
path = str(arguments.get("path") or "").strip()
|
|
142
|
+
source_path = str(arguments.get("source_path") or "").strip()
|
|
143
|
+
destination_path = str(arguments.get("destination_path") or "").strip()
|
|
144
|
+
ops = arguments.get("edits")
|
|
145
|
+
op_names: list[str] = []
|
|
146
|
+
if isinstance(ops, list):
|
|
147
|
+
for raw in ops:
|
|
148
|
+
if isinstance(raw, dict):
|
|
149
|
+
op = raw.get("op")
|
|
150
|
+
if isinstance(op, str):
|
|
151
|
+
op_names.append(op.strip())
|
|
152
|
+
op_signature = ",".join(op_names[:5])
|
|
153
|
+
return "|".join(
|
|
154
|
+
[
|
|
155
|
+
name,
|
|
156
|
+
f"path={path}",
|
|
157
|
+
f"source={source_path}",
|
|
158
|
+
f"destination={destination_path}",
|
|
159
|
+
f"ops={op_signature}",
|
|
160
|
+
]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _append_recent_exploration_path(
|
|
165
|
+
*,
|
|
166
|
+
paths: list[str],
|
|
167
|
+
candidate: str | None,
|
|
168
|
+
max_items: int = MAX_RECENT_EXPLORATION_PATHS,
|
|
169
|
+
) -> None:
|
|
170
|
+
if not candidate:
|
|
171
|
+
return
|
|
172
|
+
normalized = candidate.strip()
|
|
173
|
+
if not normalized or normalized == ".":
|
|
174
|
+
return
|
|
175
|
+
existing_idx = None
|
|
176
|
+
for idx, item in enumerate(paths):
|
|
177
|
+
if item.casefold() == normalized.casefold():
|
|
178
|
+
existing_idx = idx
|
|
179
|
+
break
|
|
180
|
+
if existing_idx is not None:
|
|
181
|
+
paths.pop(existing_idx)
|
|
182
|
+
paths.append(normalized)
|
|
183
|
+
if len(paths) > max_items:
|
|
184
|
+
del paths[0 : len(paths) - max_items]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _looks_like_unexecuted_tool_call_markup(text: str) -> bool:
|
|
188
|
+
normalized = str(text or "").strip().lower()
|
|
189
|
+
if not normalized:
|
|
190
|
+
return False
|
|
191
|
+
if "dsml" in normalized and ("tool_calls" in normalized or "invoke" in normalized):
|
|
192
|
+
return True
|
|
193
|
+
return any(marker in normalized for marker in _UNEXECUTED_TOOL_CALL_MARKUP_MARKERS)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _extract_successful_exploration_paths(
|
|
197
|
+
*,
|
|
198
|
+
root: Path,
|
|
199
|
+
tool_name: str,
|
|
200
|
+
arguments: dict[str, Any],
|
|
201
|
+
result: dict[str, Any],
|
|
202
|
+
max_items: int = MAX_POST_EXPLORE_ANCHOR_PATHS,
|
|
203
|
+
) -> list[str]:
|
|
204
|
+
normalized_tool = tool_name.strip().lower()
|
|
205
|
+
out: list[str] = []
|
|
206
|
+
|
|
207
|
+
for key in ("path", "root_path"):
|
|
208
|
+
value = arguments.get(key)
|
|
209
|
+
if isinstance(value, str):
|
|
210
|
+
normalized = _normalize_repo_relative_hint_path(root=root, raw=value)
|
|
211
|
+
if normalized and not any(
|
|
212
|
+
existing.casefold() == normalized.casefold() for existing in out
|
|
213
|
+
):
|
|
214
|
+
out.append(normalized)
|
|
215
|
+
if len(out) >= max_items:
|
|
216
|
+
return out
|
|
217
|
+
|
|
218
|
+
if normalized_tool == "fs_list":
|
|
219
|
+
result_root = _normalize_repo_relative_hint_path(
|
|
220
|
+
root=root, raw=str(result.get("root") or "")
|
|
221
|
+
)
|
|
222
|
+
entries = result.get("entries")
|
|
223
|
+
if isinstance(entries, list):
|
|
224
|
+
for entry in entries[: max_items * 3]:
|
|
225
|
+
if not isinstance(entry, dict):
|
|
226
|
+
continue
|
|
227
|
+
rel = str(entry.get("path") or "")
|
|
228
|
+
if not rel:
|
|
229
|
+
continue
|
|
230
|
+
if result_root and result_root != ".":
|
|
231
|
+
combined = f"{result_root.rstrip('/')}/{rel.lstrip('./')}"
|
|
232
|
+
else:
|
|
233
|
+
combined = rel
|
|
234
|
+
normalized = _normalize_repo_relative_hint_path(root=root, raw=combined)
|
|
235
|
+
if normalized and not any(
|
|
236
|
+
existing.casefold() == normalized.casefold() for existing in out
|
|
237
|
+
):
|
|
238
|
+
out.append(normalized)
|
|
239
|
+
if len(out) >= max_items:
|
|
240
|
+
return out
|
|
241
|
+
|
|
242
|
+
if normalized_tool == "search_rg":
|
|
243
|
+
matches = result.get("matches")
|
|
244
|
+
if isinstance(matches, list):
|
|
245
|
+
for match in matches[: max_items * 3]:
|
|
246
|
+
if not isinstance(match, dict):
|
|
247
|
+
continue
|
|
248
|
+
normalized = _normalize_repo_relative_hint_path(
|
|
249
|
+
root=root,
|
|
250
|
+
raw=str(match.get("path") or ""),
|
|
251
|
+
)
|
|
252
|
+
if normalized and not any(
|
|
253
|
+
existing.casefold() == normalized.casefold() for existing in out
|
|
254
|
+
):
|
|
255
|
+
out.append(normalized)
|
|
256
|
+
if len(out) >= max_items:
|
|
257
|
+
return out
|
|
258
|
+
|
|
259
|
+
if normalized_tool == "subagent_run":
|
|
260
|
+
subagent_result = str(result.get("result") or "")
|
|
261
|
+
for candidate in _extract_repo_relative_paths_from_text(
|
|
262
|
+
root=root,
|
|
263
|
+
text=subagent_result,
|
|
264
|
+
max_items=max_items,
|
|
265
|
+
):
|
|
266
|
+
if any(existing.casefold() == candidate.casefold() for existing in out):
|
|
267
|
+
continue
|
|
268
|
+
out.append(candidate)
|
|
269
|
+
if len(out) >= max_items:
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
return out
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _is_successful_subagent_run(
|
|
276
|
+
*,
|
|
277
|
+
tool_name: str,
|
|
278
|
+
arguments: dict[str, Any],
|
|
279
|
+
status: str,
|
|
280
|
+
result: dict[str, Any],
|
|
281
|
+
) -> bool:
|
|
282
|
+
if tool_name.strip().lower() != "subagent_run":
|
|
283
|
+
return False
|
|
284
|
+
if status == "failed":
|
|
285
|
+
return False
|
|
286
|
+
return bool(str(result.get("subagent") or arguments.get("name") or "").strip())
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _build_post_explore_bootstrap_nudge(
|
|
290
|
+
*,
|
|
291
|
+
anchor_paths: list[str],
|
|
292
|
+
language: str = "",
|
|
293
|
+
explicit_language_override: bool = False,
|
|
294
|
+
) -> str:
|
|
295
|
+
message = _runtime_message(
|
|
296
|
+
"one_shot_post_explore_bootstrap_nudge",
|
|
297
|
+
language=language,
|
|
298
|
+
explicit_language_override=explicit_language_override,
|
|
299
|
+
)
|
|
300
|
+
if anchor_paths:
|
|
301
|
+
joined = ", ".join(anchor_paths[:MAX_POST_EXPLORE_ANCHOR_PATHS])
|
|
302
|
+
message += " " + _runtime_message(
|
|
303
|
+
"one_shot_post_explore_bootstrap_targets",
|
|
304
|
+
language=language,
|
|
305
|
+
explicit_language_override=explicit_language_override,
|
|
306
|
+
joined=joined,
|
|
307
|
+
)
|
|
308
|
+
return message
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _tool_categories(tool_name: str) -> set[str]:
|
|
312
|
+
metadata = get_builtin_tool_metadata(tool_name)
|
|
313
|
+
if metadata is None:
|
|
314
|
+
return set()
|
|
315
|
+
return {str(category).strip().lower() for category in metadata.categories}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _normal_tool_name(tool_name: str) -> str:
|
|
319
|
+
return str(tool_name or "").strip().lower()
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _shell_cmd(arguments: dict[str, Any] | None) -> str:
|
|
323
|
+
if not isinstance(arguments, dict):
|
|
324
|
+
return ""
|
|
325
|
+
return str(arguments.get("cmd") or arguments.get("command") or "").strip()
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _shell_exit_code(result: dict[str, Any] | None) -> int | None:
|
|
329
|
+
if not isinstance(result, dict):
|
|
330
|
+
return None
|
|
331
|
+
value = result.get("exit_code")
|
|
332
|
+
return value if isinstance(value, int) else None
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _shell_touched_paths(
|
|
336
|
+
*,
|
|
337
|
+
result: dict[str, Any] | None,
|
|
338
|
+
touched_paths: Collection[str] | None,
|
|
339
|
+
) -> tuple[str, ...]:
|
|
340
|
+
out: list[str] = []
|
|
341
|
+
for raw in touched_paths or ():
|
|
342
|
+
text = str(raw or "").strip()
|
|
343
|
+
if text:
|
|
344
|
+
out.append(text)
|
|
345
|
+
if isinstance(result, dict):
|
|
346
|
+
for key in (
|
|
347
|
+
"touched_repo_paths",
|
|
348
|
+
"material_touched_repo_paths",
|
|
349
|
+
"verification_relevant_touched_paths",
|
|
350
|
+
):
|
|
351
|
+
value = result.get(key)
|
|
352
|
+
if isinstance(value, (list, tuple, set)):
|
|
353
|
+
for raw in value:
|
|
354
|
+
text = str(raw or "").strip()
|
|
355
|
+
if text:
|
|
356
|
+
out.append(text)
|
|
357
|
+
deduped: list[str] = []
|
|
358
|
+
for item in out:
|
|
359
|
+
if not any(existing.casefold() == item.casefold() for existing in deduped):
|
|
360
|
+
deduped.append(item)
|
|
361
|
+
return tuple(deduped)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _shell_result_has_mutation_effect(
|
|
365
|
+
*,
|
|
366
|
+
result: dict[str, Any] | None,
|
|
367
|
+
touched_paths: Collection[str] | None,
|
|
368
|
+
) -> bool:
|
|
369
|
+
return bool(_shell_touched_paths(result=result, touched_paths=touched_paths))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _shell_command_is_assertive_verification(
|
|
373
|
+
*,
|
|
374
|
+
cmd: str,
|
|
375
|
+
result: dict[str, Any] | None,
|
|
376
|
+
) -> bool:
|
|
377
|
+
if not cmd:
|
|
378
|
+
return False
|
|
379
|
+
if isinstance(result, dict) and (
|
|
380
|
+
bool(result.get("trusted_shell_verification"))
|
|
381
|
+
or bool(result.get("verification_evidence_allowed"))
|
|
382
|
+
):
|
|
383
|
+
return True
|
|
384
|
+
try:
|
|
385
|
+
analysis = analyze_verification_command(cmd, trusted=False)
|
|
386
|
+
except Exception: # noqa: BLE001
|
|
387
|
+
return False
|
|
388
|
+
if analysis.rejection_reason:
|
|
389
|
+
return False
|
|
390
|
+
return analysis.evidentiary_capability == VerificationCommandEvidentiaryCapability.ASSERTIVE
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _shell_command_head(cmd: str) -> str:
|
|
394
|
+
try:
|
|
395
|
+
parts = shlex.split(cmd)
|
|
396
|
+
except ValueError:
|
|
397
|
+
return ""
|
|
398
|
+
if not parts:
|
|
399
|
+
return ""
|
|
400
|
+
head = parts[0].rsplit("/", 1)[-1].casefold()
|
|
401
|
+
if (
|
|
402
|
+
head in {"python", "python3", "node"}
|
|
403
|
+
and len(parts) >= 3
|
|
404
|
+
and parts[1]
|
|
405
|
+
in {
|
|
406
|
+
"-m",
|
|
407
|
+
"-c",
|
|
408
|
+
}
|
|
409
|
+
):
|
|
410
|
+
return f"{head} {parts[1]} {parts[2].casefold()}"
|
|
411
|
+
return head
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _shell_command_is_low_value_exploration(cmd: str) -> bool:
|
|
415
|
+
head = _shell_command_head(cmd)
|
|
416
|
+
if not head:
|
|
417
|
+
return True
|
|
418
|
+
if head in {
|
|
419
|
+
"awk",
|
|
420
|
+
"cat",
|
|
421
|
+
"cut",
|
|
422
|
+
"env",
|
|
423
|
+
"file",
|
|
424
|
+
"find",
|
|
425
|
+
"grep",
|
|
426
|
+
"head",
|
|
427
|
+
"ls",
|
|
428
|
+
"pwd",
|
|
429
|
+
"rg",
|
|
430
|
+
"sed",
|
|
431
|
+
"stat",
|
|
432
|
+
"tail",
|
|
433
|
+
"type",
|
|
434
|
+
"wc",
|
|
435
|
+
"which",
|
|
436
|
+
}:
|
|
437
|
+
return True
|
|
438
|
+
return head.startswith(("python -c", "python3 -c", "node -c"))
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _shell_command_is_focused_progress(
|
|
442
|
+
*,
|
|
443
|
+
cmd: str,
|
|
444
|
+
focused: bool,
|
|
445
|
+
result: dict[str, Any] | None,
|
|
446
|
+
) -> bool:
|
|
447
|
+
if not focused or not cmd:
|
|
448
|
+
return False
|
|
449
|
+
if _shell_command_is_low_value_exploration(cmd):
|
|
450
|
+
return False
|
|
451
|
+
exit_code = _shell_exit_code(result)
|
|
452
|
+
return exit_code is None or exit_code == 0
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _is_shell_action_progress_tool(
|
|
456
|
+
tool_name: str,
|
|
457
|
+
*,
|
|
458
|
+
arguments: dict[str, Any] | None = None,
|
|
459
|
+
result: dict[str, Any] | None = None,
|
|
460
|
+
touched_paths: Collection[str] | None = None,
|
|
461
|
+
focused: bool = False,
|
|
462
|
+
) -> bool:
|
|
463
|
+
normalized = _normal_tool_name(tool_name)
|
|
464
|
+
if normalized in _SHELL_SERVICE_PROGRESS_TOOL_NAMES:
|
|
465
|
+
return True
|
|
466
|
+
if normalized == "shell_service_status":
|
|
467
|
+
return isinstance(result, dict) and bool(result.get("ready") or result.get("healthy"))
|
|
468
|
+
if normalized != "shell_run":
|
|
469
|
+
return False
|
|
470
|
+
if _shell_result_has_mutation_effect(result=result, touched_paths=touched_paths):
|
|
471
|
+
return True
|
|
472
|
+
cmd = _shell_cmd(arguments)
|
|
473
|
+
if _shell_command_is_assertive_verification(cmd=cmd, result=result):
|
|
474
|
+
return True
|
|
475
|
+
return _shell_command_is_focused_progress(cmd=cmd, focused=focused, result=result)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _subagent_result_has_action_effect(
|
|
479
|
+
*,
|
|
480
|
+
result: dict[str, Any] | None,
|
|
481
|
+
touched_paths: Collection[str] | None,
|
|
482
|
+
) -> bool:
|
|
483
|
+
if touched_paths:
|
|
484
|
+
return True
|
|
485
|
+
if not isinstance(result, dict):
|
|
486
|
+
return False
|
|
487
|
+
candidates = [result]
|
|
488
|
+
nested_results = result.get("results")
|
|
489
|
+
if isinstance(nested_results, dict):
|
|
490
|
+
candidates.extend(
|
|
491
|
+
candidate for candidate in nested_results.values() if isinstance(candidate, dict)
|
|
492
|
+
)
|
|
493
|
+
for candidate in candidates:
|
|
494
|
+
raw_effects = candidate.get("effects")
|
|
495
|
+
effect_names = (
|
|
496
|
+
{str(effect or "").strip().lower() for effect in raw_effects if str(effect or "")}
|
|
497
|
+
if isinstance(raw_effects, (list, tuple, set))
|
|
498
|
+
else set()
|
|
499
|
+
)
|
|
500
|
+
if effect_names & {"write_workspace", "run_commands", "external_write"}:
|
|
501
|
+
return True
|
|
502
|
+
if _shell_touched_paths(result=candidate, touched_paths=None):
|
|
503
|
+
return True
|
|
504
|
+
return False
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _is_subagent_action_progress_tool(
|
|
508
|
+
tool_name: str,
|
|
509
|
+
*,
|
|
510
|
+
arguments: dict[str, Any] | None,
|
|
511
|
+
result: dict[str, Any] | None,
|
|
512
|
+
touched_paths: Collection[str] | None,
|
|
513
|
+
) -> bool:
|
|
514
|
+
normalized = _normal_tool_name(tool_name)
|
|
515
|
+
if normalized in _SUBAGENT_READ_CONTROL_TOOL_NAMES:
|
|
516
|
+
return False
|
|
517
|
+
if normalized == "subagent_spawn":
|
|
518
|
+
workspace_view = str((arguments or {}).get("workspace_view") or "shared")
|
|
519
|
+
return workspace_view.strip().lower() == "isolated"
|
|
520
|
+
return _subagent_result_has_action_effect(
|
|
521
|
+
result=result,
|
|
522
|
+
touched_paths=touched_paths,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _is_action_progress_tool(
|
|
527
|
+
tool_name: str,
|
|
528
|
+
*,
|
|
529
|
+
arguments: dict[str, Any] | None = None,
|
|
530
|
+
result: dict[str, Any] | None = None,
|
|
531
|
+
touched_paths: Collection[str] | None = None,
|
|
532
|
+
focused: bool = False,
|
|
533
|
+
) -> bool:
|
|
534
|
+
normalized = _normal_tool_name(tool_name)
|
|
535
|
+
if normalized in (_SUBAGENT_READ_CONTROL_TOOL_NAMES | _SUBAGENT_EFFECT_CLASSIFIED_TOOL_NAMES):
|
|
536
|
+
return _is_subagent_action_progress_tool(
|
|
537
|
+
normalized,
|
|
538
|
+
arguments=arguments,
|
|
539
|
+
result=result,
|
|
540
|
+
touched_paths=touched_paths,
|
|
541
|
+
)
|
|
542
|
+
if normalized in _SHELL_TOOL_NAMES:
|
|
543
|
+
return _is_shell_action_progress_tool(
|
|
544
|
+
normalized,
|
|
545
|
+
arguments=arguments,
|
|
546
|
+
result=result,
|
|
547
|
+
touched_paths=touched_paths,
|
|
548
|
+
focused=focused,
|
|
549
|
+
)
|
|
550
|
+
categories = _tool_categories(tool_name)
|
|
551
|
+
if categories:
|
|
552
|
+
return bool(categories & _ACTION_PROGRESS_TOOL_CATEGORIES)
|
|
553
|
+
return normalized in _ACTION_PROGRESS_FALLBACK_TOOL_NAMES
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _is_exploration_only_tool(
|
|
557
|
+
tool_name: str,
|
|
558
|
+
*,
|
|
559
|
+
arguments: dict[str, Any] | None = None,
|
|
560
|
+
result: dict[str, Any] | None = None,
|
|
561
|
+
touched_paths: Collection[str] | None = None,
|
|
562
|
+
focused: bool = False,
|
|
563
|
+
) -> bool:
|
|
564
|
+
normalized = _normal_tool_name(tool_name)
|
|
565
|
+
if normalized in (_SUBAGENT_READ_CONTROL_TOOL_NAMES | _SUBAGENT_EFFECT_CLASSIFIED_TOOL_NAMES):
|
|
566
|
+
return not _is_action_progress_tool(
|
|
567
|
+
normalized,
|
|
568
|
+
arguments=arguments,
|
|
569
|
+
result=result,
|
|
570
|
+
touched_paths=touched_paths,
|
|
571
|
+
focused=focused,
|
|
572
|
+
)
|
|
573
|
+
if normalized in _SHELL_TOOL_NAMES:
|
|
574
|
+
return not _is_shell_action_progress_tool(
|
|
575
|
+
normalized,
|
|
576
|
+
arguments=arguments,
|
|
577
|
+
result=result,
|
|
578
|
+
touched_paths=touched_paths,
|
|
579
|
+
focused=focused,
|
|
580
|
+
)
|
|
581
|
+
categories = _tool_categories(tool_name)
|
|
582
|
+
if categories:
|
|
583
|
+
if categories & _ACTION_PROGRESS_TOOL_CATEGORIES:
|
|
584
|
+
return False
|
|
585
|
+
return bool(categories & _EXPLORATION_TOOL_CATEGORIES)
|
|
586
|
+
return normalized in _EXPLORATION_FALLBACK_TOOL_NAMES
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def _is_failed_edit_stagnation_tool(tool_name: str) -> bool:
|
|
590
|
+
return tool_name.strip().lower() in _FAILED_EDIT_STAGNATION_TOOL_NAMES
|