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,475 @@
|
|
|
1
|
+
"""Provider-call helpers for the main agent loop.
|
|
2
|
+
|
|
3
|
+
These functions are transport/compatibility shims around ``client.chat`` and
|
|
4
|
+
LLM error classification. They are used by the agent loop, session runtime,
|
|
5
|
+
and the non-repo response path alike, and carry no routing semantics of their
|
|
6
|
+
own; they live here so the agent loop does not depend on the router module for
|
|
7
|
+
its own provider calls.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from ..language_policy import DEFAULT_REPLY_LANGUAGE, DEFAULT_REPLY_SCRIPT
|
|
17
|
+
from ..llm.base import effective_tools_for_client
|
|
18
|
+
from ..llm.types import LLMError
|
|
19
|
+
from .prompt_context import _INLINE_CODE_SPAN_RE
|
|
20
|
+
from .turn_path import (
|
|
21
|
+
_build_turn_language_system_message,
|
|
22
|
+
_normalize_turn_language_name,
|
|
23
|
+
_normalize_turn_script_name,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from .tools_assembly import ToolDef
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _llm_error_status_code(err: LLMError) -> int | None:
|
|
31
|
+
match = re.match(r"LLM error (\d{3}):", str(err or "").strip())
|
|
32
|
+
if match is None:
|
|
33
|
+
return None
|
|
34
|
+
try:
|
|
35
|
+
return int(match.group(1))
|
|
36
|
+
except ValueError:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _is_fatal_non_repo_llm_error(err: LLMError) -> bool:
|
|
41
|
+
status_code = _llm_error_status_code(err)
|
|
42
|
+
# Provider/account and request-shape failures cannot be repaired by the
|
|
43
|
+
# static clarification route. Surface them after the transport's own
|
|
44
|
+
# compatibility retries instead of disguising them as a successful
|
|
45
|
+
# "Could you clarify..." response.
|
|
46
|
+
if status_code in {400, 401, 402, 403, 404, 422, 429}:
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
msg = str(err).lower()
|
|
50
|
+
markers = (
|
|
51
|
+
"invalid_api_key",
|
|
52
|
+
"incorrect api key",
|
|
53
|
+
"api key",
|
|
54
|
+
"authentication",
|
|
55
|
+
"unauthorized",
|
|
56
|
+
"forbidden",
|
|
57
|
+
"permission denied",
|
|
58
|
+
"access denied",
|
|
59
|
+
"credit balance",
|
|
60
|
+
"purchase credits",
|
|
61
|
+
"billing",
|
|
62
|
+
"insufficient_quota",
|
|
63
|
+
"quota exhausted",
|
|
64
|
+
"quota exceeded",
|
|
65
|
+
"rate limit",
|
|
66
|
+
)
|
|
67
|
+
if any(marker in msg for marker in markers):
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
# Recognized Alysis Code MiMo trial proxy errors (trial_expired,
|
|
71
|
+
# quota_exhausted, rate_limit_exceeded, ...) must propagate so the REPL's
|
|
72
|
+
# friendly-error renderer shows an actionable message, instead of being
|
|
73
|
+
# swallowed into the generic "Could you clarify..." fallback. Lazy import
|
|
74
|
+
# avoids an llm -> agent import cycle.
|
|
75
|
+
from ..llm.openai_compat import alysis_trial_error_message
|
|
76
|
+
|
|
77
|
+
return alysis_trial_error_message(err) is not None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_stream_unsupported_error(err: LLMError) -> bool:
|
|
81
|
+
msg = str(err).lower()
|
|
82
|
+
mentions_stream = "stream" in msg or "sse" in msg
|
|
83
|
+
unsupported = "unsupported" in msg or "not support" in msg
|
|
84
|
+
bad_status = "llm error 400" in msg or "llm error 404" in msg or "llm error 422" in msg
|
|
85
|
+
return mentions_stream and (unsupported or bad_status)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _tool_schema_function_name(tool_schema: dict[str, Any]) -> str:
|
|
89
|
+
if not isinstance(tool_schema, dict):
|
|
90
|
+
return ""
|
|
91
|
+
function = tool_schema.get("function")
|
|
92
|
+
if not isinstance(function, dict):
|
|
93
|
+
return ""
|
|
94
|
+
return str(function.get("name") or "").strip()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _registered_tool_schema_list(
|
|
98
|
+
tool_defs: dict[str, ToolDef],
|
|
99
|
+
tool_list: list[dict[str, Any]] | None,
|
|
100
|
+
) -> list[dict[str, Any]]:
|
|
101
|
+
if not tool_defs:
|
|
102
|
+
return []
|
|
103
|
+
canonical_by_name = {name: tool.as_openai_tool() for name, tool in tool_defs.items()}
|
|
104
|
+
ordered: list[dict[str, Any]] = []
|
|
105
|
+
added: set[str] = set()
|
|
106
|
+
for tool_schema in tool_list or []:
|
|
107
|
+
name = _tool_schema_function_name(tool_schema)
|
|
108
|
+
if name not in canonical_by_name or name in added:
|
|
109
|
+
continue
|
|
110
|
+
ordered.append(canonical_by_name[name])
|
|
111
|
+
added.add(name)
|
|
112
|
+
for name, tool_schema in canonical_by_name.items():
|
|
113
|
+
if name not in added:
|
|
114
|
+
ordered.append(tool_schema)
|
|
115
|
+
return ordered
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _main_agent_chat(
|
|
119
|
+
*,
|
|
120
|
+
client: Any,
|
|
121
|
+
messages: list[dict[str, Any]],
|
|
122
|
+
tools: list[dict[str, Any]] | None,
|
|
123
|
+
stream: bool,
|
|
124
|
+
on_text_delta: Callable[[str], None] | None,
|
|
125
|
+
on_reasoning_delta: Callable[[str], None] | None = None,
|
|
126
|
+
temperature: float | None = None,
|
|
127
|
+
max_tokens: int | None = None,
|
|
128
|
+
cancellation_token: Any | None = None,
|
|
129
|
+
tool_choice: Any | None = None,
|
|
130
|
+
) -> Any:
|
|
131
|
+
tools = effective_tools_for_client(client, tools)
|
|
132
|
+
if tools is None:
|
|
133
|
+
tool_choice = None
|
|
134
|
+
kwargs: dict[str, Any] = {
|
|
135
|
+
"messages": messages,
|
|
136
|
+
"tools": tools,
|
|
137
|
+
"stream": stream,
|
|
138
|
+
"on_text_delta": on_text_delta,
|
|
139
|
+
"on_reasoning_delta": on_reasoning_delta,
|
|
140
|
+
"temperature": temperature,
|
|
141
|
+
}
|
|
142
|
+
# Pass the token only when present so older/test clients keep working via the
|
|
143
|
+
# TypeError fallbacks below; clients that accept it can abort an in-flight
|
|
144
|
+
# request the instant the user interrupts (even before the first token).
|
|
145
|
+
if cancellation_token is not None:
|
|
146
|
+
kwargs["cancellation_token"] = cancellation_token
|
|
147
|
+
if max_tokens is not None:
|
|
148
|
+
# Deliberately not part of the compatibility-removal loop: a bounded
|
|
149
|
+
# control-plane request must fail rather than silently become unbounded.
|
|
150
|
+
kwargs["max_tokens"] = max_tokens
|
|
151
|
+
if tool_choice is not None:
|
|
152
|
+
kwargs["tool_choice"] = tool_choice
|
|
153
|
+
for compatibility_arg in (
|
|
154
|
+
"cancellation_token",
|
|
155
|
+
"on_reasoning_delta",
|
|
156
|
+
"temperature",
|
|
157
|
+
"tool_choice",
|
|
158
|
+
):
|
|
159
|
+
try:
|
|
160
|
+
return client.chat(**kwargs)
|
|
161
|
+
except TypeError:
|
|
162
|
+
kwargs.pop(compatibility_arg, None)
|
|
163
|
+
return client.chat(**kwargs)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _client_supports_tool_calling(client: Any) -> bool:
|
|
167
|
+
return getattr(client, "supports_tool_calling", True) is not False
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _client_reasoning_or_thinking_active(client: Any) -> bool:
|
|
171
|
+
for attr in ("reasoning_active", "thinking_active"):
|
|
172
|
+
value = getattr(client, attr, None)
|
|
173
|
+
if isinstance(value, bool):
|
|
174
|
+
return value
|
|
175
|
+
if getattr(client, "enable_thinking", None) is True:
|
|
176
|
+
return True
|
|
177
|
+
reasoning_effort = str(getattr(client, "reasoning_effort", "") or "").strip().casefold()
|
|
178
|
+
return bool(reasoning_effort and reasoning_effort != "none")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _client_supports_forced_tool_choice(client: Any) -> bool:
|
|
182
|
+
return (
|
|
183
|
+
getattr(client, "supports_forced_tool_choice", False) is True
|
|
184
|
+
and _client_supports_tool_calling(client)
|
|
185
|
+
and not _client_reasoning_or_thinking_active(client)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _function_tool_choice(tool_name: str) -> dict[str, Any]:
|
|
190
|
+
return {"type": "function", "function": {"name": str(tool_name)}}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _safe_forced_tool_choice_for_recovery(
|
|
194
|
+
*,
|
|
195
|
+
client: Any,
|
|
196
|
+
tools: list[dict[str, Any]] | None,
|
|
197
|
+
preferred_tool_names: tuple[str, ...],
|
|
198
|
+
) -> dict[str, Any] | None:
|
|
199
|
+
if not _client_supports_forced_tool_choice(client):
|
|
200
|
+
return None
|
|
201
|
+
available: set[str] = set()
|
|
202
|
+
for tool in tools or []:
|
|
203
|
+
if not isinstance(tool, dict):
|
|
204
|
+
continue
|
|
205
|
+
name = _tool_schema_function_name(tool)
|
|
206
|
+
if name:
|
|
207
|
+
available.add(name)
|
|
208
|
+
for name in preferred_tool_names:
|
|
209
|
+
if name in available:
|
|
210
|
+
return _function_tool_choice(name)
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# Ephemeral request-message injection
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
_VOLATILE_HOST_CONTEXT_PREFIXES = (
|
|
220
|
+
"<workspace_binding_context>",
|
|
221
|
+
"<task_brief>",
|
|
222
|
+
"<environment_context>",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _is_volatile_host_context_message(message: dict[str, Any]) -> bool:
|
|
227
|
+
if str(message.get("role") or "").strip().lower() != "user":
|
|
228
|
+
return False
|
|
229
|
+
content = message.get("content")
|
|
230
|
+
if not isinstance(content, str):
|
|
231
|
+
return False
|
|
232
|
+
stripped = content.lstrip()
|
|
233
|
+
return stripped.startswith(_VOLATILE_HOST_CONTEXT_PREFIXES)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _request_messages_with_volatile_suffix(
|
|
237
|
+
*,
|
|
238
|
+
messages: list[dict[str, Any]],
|
|
239
|
+
turn_system_prompts: list[str] | tuple[str, ...] | None = None,
|
|
240
|
+
turn_user_contexts: list[str] | tuple[str, ...] | None = None,
|
|
241
|
+
step_system_prompts: list[str] | tuple[str, ...] | None = None,
|
|
242
|
+
) -> list[dict[str, Any]]:
|
|
243
|
+
"""Keep every call-varying host message after reusable transcript history.
|
|
244
|
+
|
|
245
|
+
Prompt caches match byte prefixes. Never place a replaceable host context or
|
|
246
|
+
an ephemeral turn/step message ahead of stable conversation content. This is
|
|
247
|
+
a request-local reorder: persistent history and the information sent to the
|
|
248
|
+
model are unchanged.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
stable_messages: list[dict[str, Any]] = []
|
|
252
|
+
volatile_messages: list[dict[str, Any]] = []
|
|
253
|
+
for message in messages:
|
|
254
|
+
target = (
|
|
255
|
+
volatile_messages if _is_volatile_host_context_message(message) else stable_messages
|
|
256
|
+
)
|
|
257
|
+
target.append(message)
|
|
258
|
+
|
|
259
|
+
cleaned_turn_system = [str(prompt or "").strip() for prompt in (turn_system_prompts or [])]
|
|
260
|
+
cleaned_turn_user = [str(content or "").strip() for content in (turn_user_contexts or [])]
|
|
261
|
+
cleaned_step_system = [str(prompt or "").strip() for prompt in (step_system_prompts or [])]
|
|
262
|
+
return [
|
|
263
|
+
*stable_messages,
|
|
264
|
+
*volatile_messages,
|
|
265
|
+
*({"role": "system", "content": prompt} for prompt in cleaned_turn_system if prompt),
|
|
266
|
+
*({"role": "user", "content": content} for content in cleaned_turn_user if content),
|
|
267
|
+
*({"role": "system", "content": prompt} for prompt in cleaned_step_system if prompt),
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _request_messages_with_ephemeral_system_prompts(
|
|
272
|
+
*,
|
|
273
|
+
messages: list[dict[str, Any]],
|
|
274
|
+
insert_index: int,
|
|
275
|
+
prompts: list[str] | tuple[str, ...] | None,
|
|
276
|
+
) -> list[dict[str, Any]]:
|
|
277
|
+
cleaned_prompts = [str(prompt or "").strip() for prompt in (prompts or [])]
|
|
278
|
+
cleaned_prompts = [prompt for prompt in cleaned_prompts if prompt]
|
|
279
|
+
if not cleaned_prompts:
|
|
280
|
+
return list(messages)
|
|
281
|
+
bounded_index = max(0, min(len(messages), insert_index))
|
|
282
|
+
injected = [{"role": "system", "content": prompt} for prompt in cleaned_prompts]
|
|
283
|
+
return list(messages[:bounded_index]) + injected + list(messages[bounded_index:])
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _request_messages_with_ephemeral_system_prompt_suffixes(
|
|
287
|
+
*,
|
|
288
|
+
messages: list[dict[str, Any]],
|
|
289
|
+
prompts: list[str] | tuple[str, ...] | None,
|
|
290
|
+
) -> list[dict[str, Any]]:
|
|
291
|
+
cleaned_prompts = [str(prompt or "").strip() for prompt in (prompts or [])]
|
|
292
|
+
cleaned_prompts = [prompt for prompt in cleaned_prompts if prompt]
|
|
293
|
+
if not cleaned_prompts:
|
|
294
|
+
return list(messages)
|
|
295
|
+
injected = [{"role": "system", "content": prompt} for prompt in cleaned_prompts]
|
|
296
|
+
return list(messages) + injected
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _request_messages_with_ephemeral_user_messages(
|
|
300
|
+
*,
|
|
301
|
+
messages: list[dict[str, Any]],
|
|
302
|
+
insert_index: int,
|
|
303
|
+
contents: list[str] | tuple[str, ...] | None,
|
|
304
|
+
) -> list[dict[str, Any]]:
|
|
305
|
+
cleaned_contents = [str(content or "").strip() for content in (contents or [])]
|
|
306
|
+
cleaned_contents = [content for content in cleaned_contents if content]
|
|
307
|
+
if not cleaned_contents:
|
|
308
|
+
return list(messages)
|
|
309
|
+
bounded_index = max(0, min(len(messages), insert_index))
|
|
310
|
+
injected = [{"role": "user", "content": content} for content in cleaned_contents]
|
|
311
|
+
return list(messages[:bounded_index]) + injected + list(messages[bounded_index:])
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ---------------------------------------------------------------------------
|
|
315
|
+
# Final-summary language rewrite
|
|
316
|
+
# ---------------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
_FINAL_SUMMARY_REWRITE_SYSTEM_PROMPT = """Rewrite one successful coding-task final summary into the selected reply language/script.
|
|
320
|
+
|
|
321
|
+
Output only the rewritten summary.
|
|
322
|
+
Preserve technical meaning exactly.
|
|
323
|
+
Do not add or remove implementation claims, test claims, warnings, or blockers.
|
|
324
|
+
Keep file paths, code identifiers, CLI commands, config keys, JSON keys, fenced code blocks, and inline code exactly as written.
|
|
325
|
+
If the summary is already in the requested language/script, return it unchanged.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
_FENCED_CODE_BLOCK_RE = re.compile(r"```.*?```", re.DOTALL)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
_REWRITE_PROTECTED_TOKEN_RE = re.compile(r"\b[A-Za-z0-9_./:-]*[./_-][A-Za-z0-9_./:-]+\b")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _non_repo_chat(
|
|
336
|
+
*,
|
|
337
|
+
client: Any,
|
|
338
|
+
messages: list[dict[str, Any]],
|
|
339
|
+
temperature: float,
|
|
340
|
+
tools: list[dict[str, Any]] | None = None,
|
|
341
|
+
stream: bool = False,
|
|
342
|
+
on_text_delta: Callable[[str], None] | None = None,
|
|
343
|
+
on_reasoning_delta: Callable[[str], None] | None = None,
|
|
344
|
+
) -> Any:
|
|
345
|
+
tools = effective_tools_for_client(client, tools)
|
|
346
|
+
base: dict[str, Any] = {"messages": messages, "tools": tools, "stream": stream}
|
|
347
|
+
try:
|
|
348
|
+
return client.chat(
|
|
349
|
+
**base,
|
|
350
|
+
on_text_delta=on_text_delta,
|
|
351
|
+
on_reasoning_delta=on_reasoning_delta,
|
|
352
|
+
temperature=temperature,
|
|
353
|
+
)
|
|
354
|
+
except TypeError:
|
|
355
|
+
pass
|
|
356
|
+
try:
|
|
357
|
+
return client.chat(**base, on_text_delta=on_text_delta, temperature=temperature)
|
|
358
|
+
except TypeError:
|
|
359
|
+
pass
|
|
360
|
+
try:
|
|
361
|
+
return client.chat(**base, temperature=temperature)
|
|
362
|
+
except TypeError:
|
|
363
|
+
return client.chat(**base)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _extract_rewrite_protected_fragments(text: str) -> list[str]:
|
|
367
|
+
fragments: list[str] = []
|
|
368
|
+
seen: set[str] = set()
|
|
369
|
+
|
|
370
|
+
def _add(fragment: str) -> None:
|
|
371
|
+
candidate = str(fragment or "")
|
|
372
|
+
if not candidate or candidate in seen:
|
|
373
|
+
return
|
|
374
|
+
seen.add(candidate)
|
|
375
|
+
fragments.append(candidate)
|
|
376
|
+
|
|
377
|
+
for fragment in _FENCED_CODE_BLOCK_RE.findall(str(text or "")):
|
|
378
|
+
_add(fragment)
|
|
379
|
+
for fragment in _INLINE_CODE_SPAN_RE.findall(str(text or "")):
|
|
380
|
+
_add(fragment)
|
|
381
|
+
for token in _REWRITE_PROTECTED_TOKEN_RE.findall(str(text or "")):
|
|
382
|
+
_add(token)
|
|
383
|
+
return fragments
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _rewritten_text_preserves_technical_tokens(original: str, rewritten: str) -> bool:
|
|
387
|
+
if not original.strip():
|
|
388
|
+
return True
|
|
389
|
+
if not rewritten.strip():
|
|
390
|
+
return False
|
|
391
|
+
protected = _extract_rewrite_protected_fragments(original)
|
|
392
|
+
return all(fragment in rewritten for fragment in protected)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _rewrite_final_summary_for_language(
|
|
396
|
+
*,
|
|
397
|
+
client: Any,
|
|
398
|
+
final_text: str,
|
|
399
|
+
language: str = "",
|
|
400
|
+
script: str = "",
|
|
401
|
+
explicit_language_override: bool = False,
|
|
402
|
+
record_usage: Callable[..., Any] | None = None,
|
|
403
|
+
) -> tuple[str, dict[str, Any] | None]:
|
|
404
|
+
clean = str(final_text or "").strip()
|
|
405
|
+
if not clean:
|
|
406
|
+
return clean, None
|
|
407
|
+
|
|
408
|
+
resolved_language = _normalize_turn_language_name(language)
|
|
409
|
+
resolved_script = _normalize_turn_script_name(script)
|
|
410
|
+
if not (resolved_language or resolved_script):
|
|
411
|
+
return clean, None
|
|
412
|
+
if (
|
|
413
|
+
not explicit_language_override
|
|
414
|
+
and resolved_language == DEFAULT_REPLY_LANGUAGE
|
|
415
|
+
and resolved_script in {"", DEFAULT_REPLY_SCRIPT}
|
|
416
|
+
):
|
|
417
|
+
return clean, None
|
|
418
|
+
|
|
419
|
+
payload: dict[str, Any] = {
|
|
420
|
+
"language": resolved_language,
|
|
421
|
+
"script": resolved_script,
|
|
422
|
+
"explicit_language_override": explicit_language_override,
|
|
423
|
+
}
|
|
424
|
+
language_directive = _build_turn_language_system_message(
|
|
425
|
+
resolved_language,
|
|
426
|
+
resolved_script,
|
|
427
|
+
explicit_language_override=explicit_language_override,
|
|
428
|
+
)
|
|
429
|
+
if not language_directive:
|
|
430
|
+
payload.update({"status": "skipped", "reason": "no_language_directive"})
|
|
431
|
+
return clean, payload
|
|
432
|
+
|
|
433
|
+
protected_fragments = _extract_rewrite_protected_fragments(clean)
|
|
434
|
+
payload["protected_fragment_count"] = len(protected_fragments)
|
|
435
|
+
rewrite_messages = [
|
|
436
|
+
{"role": "system", "content": _FINAL_SUMMARY_REWRITE_SYSTEM_PROMPT},
|
|
437
|
+
{"role": "system", "content": language_directive},
|
|
438
|
+
{
|
|
439
|
+
"role": "user",
|
|
440
|
+
"content": (
|
|
441
|
+
"Rewrite this final assistant summary only.\n\n"
|
|
442
|
+
"<assistant_summary>\n"
|
|
443
|
+
f"{clean}\n"
|
|
444
|
+
"</assistant_summary>"
|
|
445
|
+
),
|
|
446
|
+
},
|
|
447
|
+
]
|
|
448
|
+
try:
|
|
449
|
+
response = _non_repo_chat(client=client, messages=rewrite_messages, temperature=0.0)
|
|
450
|
+
except Exception as exc: # noqa: BLE001
|
|
451
|
+
payload.update({"status": "kept_original", "reason": "rewrite_error", "error": str(exc)})
|
|
452
|
+
return clean, payload
|
|
453
|
+
|
|
454
|
+
if record_usage is not None:
|
|
455
|
+
record_usage(
|
|
456
|
+
response=response,
|
|
457
|
+
messages=rewrite_messages,
|
|
458
|
+
tool_list=None,
|
|
459
|
+
operation="final_summary_language_rewrite",
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
rewritten = str(getattr(response, "content", "") or "").strip()
|
|
463
|
+
if not rewritten:
|
|
464
|
+
payload.update({"status": "kept_original", "reason": "empty_rewrite"})
|
|
465
|
+
return clean, payload
|
|
466
|
+
if not _rewritten_text_preserves_technical_tokens(clean, rewritten):
|
|
467
|
+
payload.update({"status": "kept_original", "reason": "protected_tokens_missing"})
|
|
468
|
+
return clean, payload
|
|
469
|
+
payload.update(
|
|
470
|
+
{
|
|
471
|
+
"status": "applied" if rewritten != clean else "unchanged",
|
|
472
|
+
"reason": "rewrite_succeeded",
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
return rewritten, payload
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from pathlib import Path, PurePosixPath
|
|
6
|
+
|
|
7
|
+
from ..runtime_artifacts import is_runtime_artifact_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MutationPathCategory(StrEnum):
|
|
11
|
+
BENIGN_RUNTIME_ARTIFACT = "BENIGN_RUNTIME_ARTIFACT"
|
|
12
|
+
MATERIAL_SOURCE_OR_CONFIG = "MATERIAL_SOURCE_OR_CONFIG"
|
|
13
|
+
MATERIAL_DELIVERABLE = "MATERIAL_DELIVERABLE"
|
|
14
|
+
UNKNOWN_MATERIAL = "UNKNOWN_MATERIAL"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_SOURCE_DIR_NAMES = {
|
|
18
|
+
"app",
|
|
19
|
+
"apps",
|
|
20
|
+
"bin",
|
|
21
|
+
"cmd",
|
|
22
|
+
"lib",
|
|
23
|
+
"pkg",
|
|
24
|
+
"packages",
|
|
25
|
+
"server",
|
|
26
|
+
"src",
|
|
27
|
+
"test",
|
|
28
|
+
"tests",
|
|
29
|
+
}
|
|
30
|
+
_SOURCE_SUFFIXES = {
|
|
31
|
+
".c",
|
|
32
|
+
".cc",
|
|
33
|
+
".cpp",
|
|
34
|
+
".cs",
|
|
35
|
+
".css",
|
|
36
|
+
".go",
|
|
37
|
+
".h",
|
|
38
|
+
".hpp",
|
|
39
|
+
".html",
|
|
40
|
+
".java",
|
|
41
|
+
".js",
|
|
42
|
+
".jsx",
|
|
43
|
+
".kt",
|
|
44
|
+
".mjs",
|
|
45
|
+
".php",
|
|
46
|
+
".py",
|
|
47
|
+
".rb",
|
|
48
|
+
".rs",
|
|
49
|
+
".scala",
|
|
50
|
+
".sh",
|
|
51
|
+
".swift",
|
|
52
|
+
".ts",
|
|
53
|
+
".tsx",
|
|
54
|
+
".vue",
|
|
55
|
+
}
|
|
56
|
+
_CONFIG_SUFFIXES = {".cfg", ".conf", ".ini", ".json", ".toml", ".yaml", ".yml"}
|
|
57
|
+
_CONFIG_FILENAMES = {
|
|
58
|
+
".env",
|
|
59
|
+
"cargo.lock",
|
|
60
|
+
"cargo.toml",
|
|
61
|
+
"dockerfile",
|
|
62
|
+
"go.mod",
|
|
63
|
+
"go.sum",
|
|
64
|
+
"justfile",
|
|
65
|
+
"makefile",
|
|
66
|
+
"package-lock.json",
|
|
67
|
+
"package.json",
|
|
68
|
+
"pnpm-lock.yaml",
|
|
69
|
+
"pyproject.toml",
|
|
70
|
+
"requirements.txt",
|
|
71
|
+
"setup.cfg",
|
|
72
|
+
"setup.py",
|
|
73
|
+
"tsconfig.json",
|
|
74
|
+
"yarn.lock",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class MutationPathClassification:
|
|
80
|
+
path: str
|
|
81
|
+
category: MutationPathCategory
|
|
82
|
+
reason: str
|
|
83
|
+
tracked: bool | None = None
|
|
84
|
+
existed_before: bool | None = None
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def is_material(self) -> bool:
|
|
88
|
+
return self.category != MutationPathCategory.BENIGN_RUNTIME_ARTIFACT
|
|
89
|
+
|
|
90
|
+
def as_payload(self) -> dict[str, object]:
|
|
91
|
+
return {
|
|
92
|
+
"path": self.path,
|
|
93
|
+
"category": self.category.value,
|
|
94
|
+
"reason": self.reason,
|
|
95
|
+
"tracked": self.tracked,
|
|
96
|
+
"existed_before": self.existed_before,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _normalize_rel_path(path: str) -> str:
|
|
101
|
+
cleaned = str(path or "").strip().replace("\\", "/")
|
|
102
|
+
while cleaned.startswith("./"):
|
|
103
|
+
cleaned = cleaned[2:]
|
|
104
|
+
return cleaned
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _requested_paths_contain(path: str, requested_paths: set[str] | None) -> bool:
|
|
108
|
+
if not requested_paths:
|
|
109
|
+
return False
|
|
110
|
+
key = path.casefold()
|
|
111
|
+
return any(key == _normalize_rel_path(item).casefold() for item in requested_paths)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _looks_like_source_or_config_path(path: str) -> bool:
|
|
115
|
+
pure = PurePosixPath(path)
|
|
116
|
+
parts = tuple(part.casefold() for part in pure.parts if part not in {"", "."})
|
|
117
|
+
if not parts:
|
|
118
|
+
return False
|
|
119
|
+
name = pure.name.casefold()
|
|
120
|
+
suffix = pure.suffix.casefold()
|
|
121
|
+
if name in _CONFIG_FILENAMES:
|
|
122
|
+
return True
|
|
123
|
+
if suffix in _CONFIG_SUFFIXES:
|
|
124
|
+
return True
|
|
125
|
+
if suffix in _SOURCE_SUFFIXES:
|
|
126
|
+
return True
|
|
127
|
+
return bool(set(parts[:-1]) & _SOURCE_DIR_NAMES)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def classify_mutation_path(
|
|
131
|
+
path: str,
|
|
132
|
+
*,
|
|
133
|
+
root: Path | None = None,
|
|
134
|
+
requested_paths: set[str] | None = None,
|
|
135
|
+
tracked: bool | None = None,
|
|
136
|
+
existed_before: bool | None = None,
|
|
137
|
+
command_was_verification: bool = False,
|
|
138
|
+
) -> MutationPathClassification:
|
|
139
|
+
normalized = _normalize_rel_path(path)
|
|
140
|
+
if not normalized:
|
|
141
|
+
return MutationPathClassification(
|
|
142
|
+
path="",
|
|
143
|
+
category=MutationPathCategory.UNKNOWN_MATERIAL,
|
|
144
|
+
reason="empty_path",
|
|
145
|
+
tracked=tracked,
|
|
146
|
+
existed_before=existed_before,
|
|
147
|
+
)
|
|
148
|
+
if is_runtime_artifact_path(normalized, root=root):
|
|
149
|
+
return MutationPathClassification(
|
|
150
|
+
path=normalized,
|
|
151
|
+
category=MutationPathCategory.BENIGN_RUNTIME_ARTIFACT,
|
|
152
|
+
reason="runtime_artifact_policy",
|
|
153
|
+
tracked=tracked,
|
|
154
|
+
existed_before=existed_before,
|
|
155
|
+
)
|
|
156
|
+
if _requested_paths_contain(normalized, requested_paths) or existed_before is False:
|
|
157
|
+
return MutationPathClassification(
|
|
158
|
+
path=normalized,
|
|
159
|
+
category=MutationPathCategory.MATERIAL_DELIVERABLE,
|
|
160
|
+
reason="requested_or_new_output",
|
|
161
|
+
tracked=tracked,
|
|
162
|
+
existed_before=existed_before,
|
|
163
|
+
)
|
|
164
|
+
if _looks_like_source_or_config_path(normalized):
|
|
165
|
+
return MutationPathClassification(
|
|
166
|
+
path=normalized,
|
|
167
|
+
category=MutationPathCategory.MATERIAL_SOURCE_OR_CONFIG,
|
|
168
|
+
reason="source_or_config_path",
|
|
169
|
+
tracked=tracked,
|
|
170
|
+
existed_before=existed_before,
|
|
171
|
+
)
|
|
172
|
+
return MutationPathClassification(
|
|
173
|
+
path=normalized,
|
|
174
|
+
category=MutationPathCategory.UNKNOWN_MATERIAL,
|
|
175
|
+
reason="conservative_material_fallback",
|
|
176
|
+
tracked=tracked,
|
|
177
|
+
existed_before=existed_before,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def classify_mutation_paths(
|
|
182
|
+
paths: set[str] | list[str] | tuple[str, ...],
|
|
183
|
+
*,
|
|
184
|
+
root: Path | None = None,
|
|
185
|
+
requested_paths: set[str] | None = None,
|
|
186
|
+
command_was_verification: bool = False,
|
|
187
|
+
) -> tuple[MutationPathClassification, ...]:
|
|
188
|
+
return tuple(
|
|
189
|
+
classify_mutation_path(
|
|
190
|
+
path,
|
|
191
|
+
root=root,
|
|
192
|
+
requested_paths=requested_paths,
|
|
193
|
+
command_was_verification=command_was_verification,
|
|
194
|
+
)
|
|
195
|
+
for path in sorted({str(item) for item in paths if str(item).strip()})
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def material_mutation_paths(
|
|
200
|
+
paths: set[str] | list[str] | tuple[str, ...],
|
|
201
|
+
*,
|
|
202
|
+
root: Path | None = None,
|
|
203
|
+
requested_paths: set[str] | None = None,
|
|
204
|
+
command_was_verification: bool = False,
|
|
205
|
+
) -> list[str]:
|
|
206
|
+
return [
|
|
207
|
+
item.path
|
|
208
|
+
for item in classify_mutation_paths(
|
|
209
|
+
paths,
|
|
210
|
+
root=root,
|
|
211
|
+
requested_paths=requested_paths,
|
|
212
|
+
command_was_verification=command_was_verification,
|
|
213
|
+
)
|
|
214
|
+
if item.is_material
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def benign_runtime_mutation_paths(
|
|
219
|
+
paths: set[str] | list[str] | tuple[str, ...],
|
|
220
|
+
*,
|
|
221
|
+
root: Path | None = None,
|
|
222
|
+
) -> list[str]:
|
|
223
|
+
return [
|
|
224
|
+
item.path
|
|
225
|
+
for item in classify_mutation_paths(paths, root=root)
|
|
226
|
+
if item.category == MutationPathCategory.BENIGN_RUNTIME_ARTIFACT
|
|
227
|
+
]
|