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,304 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from ..frontmatter_utils import parse_frontmatter_yaml, split_frontmatter
|
|
8
|
+
from .prompting import (
|
|
9
|
+
EXPLICIT_SKILL_CONTEXT_TOTAL_MAX_CHARS,
|
|
10
|
+
EXPLICIT_SKILL_DESCRIPTION_DISPLAY_MAX_CHARS,
|
|
11
|
+
EXPLICIT_SKILL_ENTRYPOINT_MAX_CHARS,
|
|
12
|
+
EXPLICIT_SKILL_NAME_DISPLAY_MAX_CHARS,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
SKILL_ENTRYPOINT_WARNING_CHARS = EXPLICIT_SKILL_ENTRYPOINT_MAX_CHARS
|
|
16
|
+
SKILL_NAME_WARNING_CHARS = EXPLICIT_SKILL_NAME_DISPLAY_MAX_CHARS
|
|
17
|
+
SKILL_DESCRIPTION_WARNING_CHARS = EXPLICIT_SKILL_DESCRIPTION_DISPLAY_MAX_CHARS
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class SkillValidationIssue:
|
|
22
|
+
severity: Literal["error", "warning"]
|
|
23
|
+
message: str
|
|
24
|
+
path: Path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class SkillValidationResult:
|
|
29
|
+
bundle_path: Path
|
|
30
|
+
entry_path: Path
|
|
31
|
+
valid: bool
|
|
32
|
+
name: str = ""
|
|
33
|
+
description: str = ""
|
|
34
|
+
instructions: str = ""
|
|
35
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
issues: tuple[SkillValidationIssue, ...] = ()
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def errors(self) -> tuple[SkillValidationIssue, ...]:
|
|
40
|
+
return tuple(issue for issue in self.issues if issue.severity == "error")
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def warnings(self) -> tuple[SkillValidationIssue, ...]:
|
|
44
|
+
return tuple(issue for issue in self.issues if issue.severity == "warning")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def validate_skill_bundle(bundle_path: Path) -> SkillValidationResult:
|
|
48
|
+
"""Validate a skill bundle without allowing filesystem faults to escape.
|
|
49
|
+
|
|
50
|
+
Skill directories are untrusted workspace input. On Windows in particular,
|
|
51
|
+
even metadata probes such as ``is_symlink()`` can raise ``PermissionError``
|
|
52
|
+
for a deny-ACL directory. Discovery must fail that one bundle closed instead
|
|
53
|
+
of making session creation fail.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
return _validate_skill_bundle(bundle_path)
|
|
58
|
+
except OSError as exc:
|
|
59
|
+
try:
|
|
60
|
+
reported_bundle = bundle_path.expanduser().absolute()
|
|
61
|
+
except OSError:
|
|
62
|
+
reported_bundle = bundle_path
|
|
63
|
+
entry_path = reported_bundle / "SKILL.md"
|
|
64
|
+
issue = SkillValidationIssue(
|
|
65
|
+
severity="error",
|
|
66
|
+
message=f"Failed to inspect skill bundle: {exc}",
|
|
67
|
+
path=reported_bundle,
|
|
68
|
+
)
|
|
69
|
+
return SkillValidationResult(
|
|
70
|
+
bundle_path=reported_bundle,
|
|
71
|
+
entry_path=entry_path,
|
|
72
|
+
valid=False,
|
|
73
|
+
issues=(issue,),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _validate_skill_bundle(bundle_path: Path) -> SkillValidationResult:
|
|
78
|
+
resolved_bundle = bundle_path.expanduser().resolve()
|
|
79
|
+
entry_path = resolved_bundle / "SKILL.md"
|
|
80
|
+
issues: list[SkillValidationIssue] = []
|
|
81
|
+
if not resolved_bundle.exists() or not resolved_bundle.is_dir():
|
|
82
|
+
issues.append(
|
|
83
|
+
SkillValidationIssue(
|
|
84
|
+
severity="error",
|
|
85
|
+
message="Skill bundle path is not a directory.",
|
|
86
|
+
path=resolved_bundle,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
return SkillValidationResult(
|
|
90
|
+
bundle_path=resolved_bundle,
|
|
91
|
+
entry_path=entry_path,
|
|
92
|
+
valid=False,
|
|
93
|
+
issues=tuple(issues),
|
|
94
|
+
)
|
|
95
|
+
if entry_path.is_symlink():
|
|
96
|
+
issues.append(
|
|
97
|
+
SkillValidationIssue(
|
|
98
|
+
severity="error",
|
|
99
|
+
message="SKILL.md may not be a symlink.",
|
|
100
|
+
path=entry_path,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
if not entry_path.exists() or not entry_path.is_file():
|
|
104
|
+
issues.append(
|
|
105
|
+
SkillValidationIssue(
|
|
106
|
+
severity="error",
|
|
107
|
+
message="SKILL.md is missing.",
|
|
108
|
+
path=entry_path,
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
return SkillValidationResult(
|
|
112
|
+
bundle_path=resolved_bundle,
|
|
113
|
+
entry_path=entry_path,
|
|
114
|
+
valid=False,
|
|
115
|
+
issues=tuple(issues),
|
|
116
|
+
)
|
|
117
|
+
try:
|
|
118
|
+
raw_text = entry_path.read_text(encoding="utf-8")
|
|
119
|
+
except UnicodeDecodeError:
|
|
120
|
+
issues.append(
|
|
121
|
+
SkillValidationIssue(
|
|
122
|
+
severity="error",
|
|
123
|
+
message="SKILL.md is not valid UTF-8 text.",
|
|
124
|
+
path=entry_path,
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
return SkillValidationResult(
|
|
128
|
+
bundle_path=resolved_bundle,
|
|
129
|
+
entry_path=entry_path,
|
|
130
|
+
valid=False,
|
|
131
|
+
issues=tuple(issues),
|
|
132
|
+
)
|
|
133
|
+
except OSError as exc:
|
|
134
|
+
issues.append(
|
|
135
|
+
SkillValidationIssue(
|
|
136
|
+
severity="error",
|
|
137
|
+
message=f"Failed to read SKILL.md: {exc}",
|
|
138
|
+
path=entry_path,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
return SkillValidationResult(
|
|
142
|
+
bundle_path=resolved_bundle,
|
|
143
|
+
entry_path=entry_path,
|
|
144
|
+
valid=False,
|
|
145
|
+
issues=tuple(issues),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
frontmatter, body = split_frontmatter(raw_text)
|
|
149
|
+
if frontmatter is None:
|
|
150
|
+
issues.append(
|
|
151
|
+
SkillValidationIssue(
|
|
152
|
+
severity="error",
|
|
153
|
+
message="missing YAML-style frontmatter in SKILL.md",
|
|
154
|
+
path=entry_path,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
return SkillValidationResult(
|
|
158
|
+
bundle_path=resolved_bundle,
|
|
159
|
+
entry_path=entry_path,
|
|
160
|
+
valid=False,
|
|
161
|
+
issues=tuple(issues),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
meta = parse_frontmatter_yaml(
|
|
165
|
+
frontmatter,
|
|
166
|
+
allowed_keys={"name", "description", "aliases"},
|
|
167
|
+
list_fields={"aliases"},
|
|
168
|
+
string_fields={"name", "description"},
|
|
169
|
+
)
|
|
170
|
+
name = str(meta.get("name") or "").strip()
|
|
171
|
+
description = str(meta.get("description") or "").strip()
|
|
172
|
+
instructions = body.strip()
|
|
173
|
+
if not name:
|
|
174
|
+
issues.append(
|
|
175
|
+
SkillValidationIssue(
|
|
176
|
+
severity="error",
|
|
177
|
+
message="Missing required frontmatter field: name.",
|
|
178
|
+
path=entry_path,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
if not description:
|
|
182
|
+
issues.append(
|
|
183
|
+
SkillValidationIssue(
|
|
184
|
+
severity="error",
|
|
185
|
+
message="Missing required frontmatter field: description.",
|
|
186
|
+
path=entry_path,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
if not instructions:
|
|
190
|
+
issues.append(
|
|
191
|
+
SkillValidationIssue(
|
|
192
|
+
severity="error",
|
|
193
|
+
message="SKILL.md body/instructions are empty.",
|
|
194
|
+
path=entry_path,
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
if name and len(name) > SKILL_NAME_WARNING_CHARS:
|
|
198
|
+
issues.append(
|
|
199
|
+
SkillValidationIssue(
|
|
200
|
+
severity="warning",
|
|
201
|
+
message=(
|
|
202
|
+
"Skill name is unusually large "
|
|
203
|
+
f"(>{SKILL_NAME_WARNING_CHARS:,} chars); explicit /skill metadata display is "
|
|
204
|
+
"bounded and may truncate the attached name preview."
|
|
205
|
+
),
|
|
206
|
+
path=entry_path,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
if description and len(description) > SKILL_DESCRIPTION_WARNING_CHARS:
|
|
210
|
+
issues.append(
|
|
211
|
+
SkillValidationIssue(
|
|
212
|
+
severity="warning",
|
|
213
|
+
message=(
|
|
214
|
+
"Skill description is unusually large "
|
|
215
|
+
f"(>{SKILL_DESCRIPTION_WARNING_CHARS:,} chars); explicit /skill metadata display "
|
|
216
|
+
"is bounded and may truncate the attached description preview."
|
|
217
|
+
),
|
|
218
|
+
path=entry_path,
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
if len(raw_text) > SKILL_ENTRYPOINT_WARNING_CHARS:
|
|
222
|
+
issues.append(
|
|
223
|
+
SkillValidationIssue(
|
|
224
|
+
severity="warning",
|
|
225
|
+
message=(
|
|
226
|
+
"SKILL.md entrypoint is unusually large "
|
|
227
|
+
f"(>{SKILL_ENTRYPOINT_WARNING_CHARS:,} chars); explicit /skill context is "
|
|
228
|
+
f"bounded to a {EXPLICIT_SKILL_CONTEXT_TOTAL_MAX_CHARS:,}-char wrapper and may "
|
|
229
|
+
"truncate the attached preview."
|
|
230
|
+
),
|
|
231
|
+
path=entry_path,
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
for optional_name in ("references", "scripts", "assets"):
|
|
235
|
+
optional_path = resolved_bundle / optional_name
|
|
236
|
+
if optional_path.is_symlink():
|
|
237
|
+
issues.append(
|
|
238
|
+
SkillValidationIssue(
|
|
239
|
+
severity="error",
|
|
240
|
+
message=f"{optional_name}/ may not be a symlink.",
|
|
241
|
+
path=optional_path,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
elif optional_path.exists() and not optional_path.is_dir():
|
|
245
|
+
issues.append(
|
|
246
|
+
SkillValidationIssue(
|
|
247
|
+
severity="error",
|
|
248
|
+
message=f"{optional_name}/ must be a directory when present.",
|
|
249
|
+
path=optional_path,
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
for candidate in resolved_bundle.rglob("*"):
|
|
253
|
+
if candidate == entry_path:
|
|
254
|
+
continue
|
|
255
|
+
if candidate.is_symlink():
|
|
256
|
+
issues.append(
|
|
257
|
+
SkillValidationIssue(
|
|
258
|
+
severity="error",
|
|
259
|
+
message="Skill bundles may not contain symlinks.",
|
|
260
|
+
path=candidate,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
break
|
|
264
|
+
total_bytes = 0
|
|
265
|
+
file_count = 0
|
|
266
|
+
for candidate in resolved_bundle.rglob("*"):
|
|
267
|
+
if not candidate.is_file():
|
|
268
|
+
continue
|
|
269
|
+
file_count += 1
|
|
270
|
+
try:
|
|
271
|
+
total_bytes += candidate.stat().st_size
|
|
272
|
+
except OSError:
|
|
273
|
+
continue
|
|
274
|
+
if total_bytes > 5 * 1024 * 1024:
|
|
275
|
+
issues.append(
|
|
276
|
+
SkillValidationIssue(
|
|
277
|
+
severity="warning",
|
|
278
|
+
message="Skill bundle is unusually large (>5 MiB).",
|
|
279
|
+
path=resolved_bundle,
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
if file_count > 256:
|
|
283
|
+
issues.append(
|
|
284
|
+
SkillValidationIssue(
|
|
285
|
+
severity="warning",
|
|
286
|
+
message="Skill bundle contains an unusually high file count (>256 files).",
|
|
287
|
+
path=resolved_bundle,
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return SkillValidationResult(
|
|
292
|
+
bundle_path=resolved_bundle,
|
|
293
|
+
entry_path=entry_path,
|
|
294
|
+
valid=not any(issue.severity == "error" for issue in issues),
|
|
295
|
+
name=name,
|
|
296
|
+
description=description,
|
|
297
|
+
instructions=instructions,
|
|
298
|
+
metadata={
|
|
299
|
+
key: value
|
|
300
|
+
for key, value in meta.items()
|
|
301
|
+
if key in {"name", "description", "aliases"} and value
|
|
302
|
+
},
|
|
303
|
+
issues=tuple(issues),
|
|
304
|
+
)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
# These remain the default limits for the opt-in ``limited`` policy and for
|
|
7
|
+
# backwards-compatible configuration files. The default ``autonomous`` policy
|
|
8
|
+
# does not consume them.
|
|
9
|
+
DEFAULT_CHAT_MAX_STEPS = 80
|
|
10
|
+
DEFAULT_TASK_MAX_STEPS = 160
|
|
11
|
+
DEFAULT_SUBAGENT_MAX_STEPS = 28
|
|
12
|
+
|
|
13
|
+
AUTONOMOUS_STEP_BUDGET_POLICY = "autonomous"
|
|
14
|
+
LIMITED_STEP_BUDGET_POLICY = "limited"
|
|
15
|
+
|
|
16
|
+
# ``adaptive`` and ``fixed`` are legacy spellings. Keeping them loadable avoids
|
|
17
|
+
# breaking existing installations while giving them the new, explicit contract:
|
|
18
|
+
# adaptive -> autonomous, fixed -> limited.
|
|
19
|
+
_STEP_BUDGET_POLICY_ALIASES = {
|
|
20
|
+
"adaptive": AUTONOMOUS_STEP_BUDGET_POLICY,
|
|
21
|
+
"fixed": LIMITED_STEP_BUDGET_POLICY,
|
|
22
|
+
}
|
|
23
|
+
VALID_STEP_BUDGET_POLICIES = {
|
|
24
|
+
AUTONOMOUS_STEP_BUDGET_POLICY,
|
|
25
|
+
LIMITED_STEP_BUDGET_POLICY,
|
|
26
|
+
*_STEP_BUDGET_POLICY_ALIASES,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _positive_int(value: Any, *, default: int) -> int:
|
|
31
|
+
try:
|
|
32
|
+
parsed = int(value)
|
|
33
|
+
except (TypeError, ValueError):
|
|
34
|
+
return max(1, int(default))
|
|
35
|
+
return max(1, parsed)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def normalize_step_budget_policy(raw_value: Any) -> str:
|
|
39
|
+
normalized = str(raw_value or "").strip().lower()
|
|
40
|
+
normalized = _STEP_BUDGET_POLICY_ALIASES.get(normalized, normalized)
|
|
41
|
+
if normalized in {AUTONOMOUS_STEP_BUDGET_POLICY, LIMITED_STEP_BUDGET_POLICY}:
|
|
42
|
+
return normalized
|
|
43
|
+
return AUTONOMOUS_STEP_BUDGET_POLICY
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def step_budget_is_autonomous(raw_value: Any) -> bool:
|
|
47
|
+
return normalize_step_budget_policy(raw_value) == AUTONOMOUS_STEP_BUDGET_POLICY
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def resolve_subagent_step_profile(subagent_name: Any) -> str | None:
|
|
51
|
+
normalized = str(subagent_name or "").strip().lower()
|
|
52
|
+
if not normalized:
|
|
53
|
+
return None
|
|
54
|
+
if normalized in {
|
|
55
|
+
"explorer",
|
|
56
|
+
"implementer",
|
|
57
|
+
"frontend-engineer",
|
|
58
|
+
"debugger",
|
|
59
|
+
"verifier",
|
|
60
|
+
"code-reviewer",
|
|
61
|
+
"visual-designer",
|
|
62
|
+
}:
|
|
63
|
+
return normalized
|
|
64
|
+
return "default"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class StepBudgetRequest:
|
|
69
|
+
kind: str
|
|
70
|
+
policy: str
|
|
71
|
+
hard_cap: int | None
|
|
72
|
+
fixed_override: int | None = None
|
|
73
|
+
mode: str | None = None
|
|
74
|
+
route: str | None = None
|
|
75
|
+
one_shot_execution: bool = False
|
|
76
|
+
one_shot_turn_intent: str | None = None
|
|
77
|
+
verification_enabled: bool = False
|
|
78
|
+
subagents_enabled: bool = False
|
|
79
|
+
subagent_name: str | None = None
|
|
80
|
+
parent_turn_budget: int | None = None
|
|
81
|
+
attempt_count: int = 1
|
|
82
|
+
image_count: int = 0
|
|
83
|
+
explicit_path_count: int = 0
|
|
84
|
+
acceptance_criteria_count: int = 0
|
|
85
|
+
estimated_files_count: int = 0
|
|
86
|
+
write_scope_count: int = 0
|
|
87
|
+
dependency_count: int = 0
|
|
88
|
+
asset_count: int = 0
|
|
89
|
+
conflict_file_count: int = 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class StepBudgetResolution:
|
|
94
|
+
kind: str
|
|
95
|
+
policy: str
|
|
96
|
+
hard_cap: int | None
|
|
97
|
+
resolved_max_steps: int | None
|
|
98
|
+
override_applied: bool
|
|
99
|
+
reason: str
|
|
100
|
+
signals_used: dict[str, Any]
|
|
101
|
+
base_steps: int | None
|
|
102
|
+
profile: str | None = None
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def unlimited(self) -> bool:
|
|
106
|
+
return self.resolved_max_steps is None
|
|
107
|
+
|
|
108
|
+
def to_payload(self) -> dict[str, Any]:
|
|
109
|
+
return {
|
|
110
|
+
"kind": self.kind,
|
|
111
|
+
"policy": self.policy,
|
|
112
|
+
"hard_cap": self.hard_cap,
|
|
113
|
+
"resolved_max_steps": self.resolved_max_steps,
|
|
114
|
+
"unlimited": self.unlimited,
|
|
115
|
+
"override_applied": self.override_applied,
|
|
116
|
+
"reason": self.reason,
|
|
117
|
+
"signals_used": dict(self.signals_used),
|
|
118
|
+
"base_steps": self.base_steps,
|
|
119
|
+
"profile": self.profile,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass
|
|
124
|
+
class StepBudgetRuntime:
|
|
125
|
+
active_turn_budget: int | None = None
|
|
126
|
+
last_resolution: StepBudgetResolution | None = None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _default_cap(kind: str) -> int:
|
|
130
|
+
return {
|
|
131
|
+
"chat_turn": DEFAULT_CHAT_MAX_STEPS,
|
|
132
|
+
"managed_task": DEFAULT_TASK_MAX_STEPS,
|
|
133
|
+
"conflict_resolution": DEFAULT_TASK_MAX_STEPS,
|
|
134
|
+
"subagent": DEFAULT_SUBAGENT_MAX_STEPS,
|
|
135
|
+
}.get(kind, DEFAULT_CHAT_MAX_STEPS)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _resolve_limited_cap(request: StepBudgetRequest, *, kind: str) -> int:
|
|
139
|
+
hard_cap = _positive_int(request.hard_cap, default=_default_cap(kind))
|
|
140
|
+
if kind == "subagent" and request.parent_turn_budget is not None:
|
|
141
|
+
hard_cap = min(
|
|
142
|
+
hard_cap,
|
|
143
|
+
_positive_int(request.parent_turn_budget, default=hard_cap),
|
|
144
|
+
)
|
|
145
|
+
return max(1, hard_cap)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _request_signals(request: StepBudgetRequest) -> dict[str, Any]:
|
|
149
|
+
"""Retain useful task-shape telemetry without using it to terminate work."""
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
"mode": request.mode,
|
|
153
|
+
"route": request.route,
|
|
154
|
+
"one_shot_execution": bool(request.one_shot_execution),
|
|
155
|
+
"one_shot_turn_intent": request.one_shot_turn_intent,
|
|
156
|
+
"verification_enabled": bool(request.verification_enabled),
|
|
157
|
+
"subagents_enabled": bool(request.subagents_enabled),
|
|
158
|
+
"subagent_name": request.subagent_name,
|
|
159
|
+
"parent_turn_budget": request.parent_turn_budget,
|
|
160
|
+
"attempt_count": request.attempt_count,
|
|
161
|
+
"image_count": request.image_count,
|
|
162
|
+
"explicit_path_count": request.explicit_path_count,
|
|
163
|
+
"acceptance_criteria_count": request.acceptance_criteria_count,
|
|
164
|
+
"estimated_files_count": request.estimated_files_count,
|
|
165
|
+
"write_scope_count": request.write_scope_count,
|
|
166
|
+
"dependency_count": request.dependency_count,
|
|
167
|
+
"asset_count": request.asset_count,
|
|
168
|
+
"conflict_file_count": request.conflict_file_count,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def resolve_step_budget(request: StepBudgetRequest) -> StepBudgetResolution:
|
|
173
|
+
"""Resolve an optional safety ceiling for one autonomous agent loop.
|
|
174
|
+
|
|
175
|
+
Autonomous execution is intentionally unbounded. A finite ceiling exists
|
|
176
|
+
only when the caller supplies an explicit override or selects the opt-in
|
|
177
|
+
limited policy. Completion, cancellation, blockers, fatal errors, and an
|
|
178
|
+
optional wall-clock deadline remain independent termination conditions.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
kind = str(request.kind or "").strip().lower() or "chat_turn"
|
|
182
|
+
policy = normalize_step_budget_policy(request.policy)
|
|
183
|
+
profile = resolve_subagent_step_profile(request.subagent_name) if kind == "subagent" else None
|
|
184
|
+
|
|
185
|
+
if request.fixed_override is not None:
|
|
186
|
+
explicit_limit = _positive_int(
|
|
187
|
+
request.fixed_override,
|
|
188
|
+
default=_default_cap(kind),
|
|
189
|
+
)
|
|
190
|
+
return StepBudgetResolution(
|
|
191
|
+
kind=kind,
|
|
192
|
+
policy=policy,
|
|
193
|
+
hard_cap=explicit_limit,
|
|
194
|
+
resolved_max_steps=explicit_limit,
|
|
195
|
+
override_applied=True,
|
|
196
|
+
reason="explicit_limit",
|
|
197
|
+
signals_used={
|
|
198
|
+
**_request_signals(request),
|
|
199
|
+
"policy": policy,
|
|
200
|
+
"explicit_limit": explicit_limit,
|
|
201
|
+
},
|
|
202
|
+
base_steps=explicit_limit,
|
|
203
|
+
profile=profile,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if policy == LIMITED_STEP_BUDGET_POLICY:
|
|
207
|
+
hard_cap = _resolve_limited_cap(request, kind=kind)
|
|
208
|
+
return StepBudgetResolution(
|
|
209
|
+
kind=kind,
|
|
210
|
+
policy=policy,
|
|
211
|
+
hard_cap=hard_cap,
|
|
212
|
+
resolved_max_steps=hard_cap,
|
|
213
|
+
override_applied=False,
|
|
214
|
+
reason="limited_policy",
|
|
215
|
+
signals_used={
|
|
216
|
+
**_request_signals(request),
|
|
217
|
+
"policy": policy,
|
|
218
|
+
"hard_cap": hard_cap,
|
|
219
|
+
},
|
|
220
|
+
base_steps=hard_cap,
|
|
221
|
+
profile=profile,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return StepBudgetResolution(
|
|
225
|
+
kind=kind,
|
|
226
|
+
policy=AUTONOMOUS_STEP_BUDGET_POLICY,
|
|
227
|
+
hard_cap=None,
|
|
228
|
+
resolved_max_steps=None,
|
|
229
|
+
override_applied=False,
|
|
230
|
+
reason="autonomous_unbounded",
|
|
231
|
+
signals_used={
|
|
232
|
+
**_request_signals(request),
|
|
233
|
+
"policy": AUTONOMOUS_STEP_BUDGET_POLICY,
|
|
234
|
+
"configured_legacy_cap": request.hard_cap,
|
|
235
|
+
},
|
|
236
|
+
base_steps=None,
|
|
237
|
+
profile=profile,
|
|
238
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from .safety.subagent_report import sanitize_subagent_report, subagent_report_evidence_text
|
|
6
|
+
|
|
7
|
+
SUBAGENT_TASK_LABEL_MAX_CHARS = 24
|
|
8
|
+
|
|
9
|
+
_LABEL_TOKEN_RE = re.compile(r"[\w.-]+", flags=re.UNICODE)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def subagent_task_label(task: object, *, requested_run_id: object = "") -> str:
|
|
13
|
+
"""Return a stable, display-safe child label without exposing the generated id."""
|
|
14
|
+
caller_label = str(requested_run_id or "").strip()
|
|
15
|
+
if caller_label:
|
|
16
|
+
return caller_label
|
|
17
|
+
|
|
18
|
+
screened = sanitize_subagent_report(str(task or "")).text
|
|
19
|
+
evidence = subagent_report_evidence_text(screened)
|
|
20
|
+
words = [
|
|
21
|
+
token
|
|
22
|
+
for token in _LABEL_TOKEN_RE.findall(evidence)
|
|
23
|
+
if any(character.isalnum() for character in token)
|
|
24
|
+
]
|
|
25
|
+
selected: list[str] = []
|
|
26
|
+
for word in words:
|
|
27
|
+
candidate = " ".join((*selected, word))
|
|
28
|
+
if len(candidate) > SUBAGENT_TASK_LABEL_MAX_CHARS:
|
|
29
|
+
break
|
|
30
|
+
selected.append(word)
|
|
31
|
+
if selected:
|
|
32
|
+
return " ".join(selected)
|
|
33
|
+
if words:
|
|
34
|
+
return words[0][:SUBAGENT_TASK_LABEL_MAX_CHARS]
|
|
35
|
+
return "task"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def subagent_identity(name: object, label: object) -> str:
|
|
39
|
+
"""Combine role and stable task identity for user-facing surfaces."""
|
|
40
|
+
role = str(name or "subagent").strip() or "subagent"
|
|
41
|
+
task_label = str(label or "").strip()
|
|
42
|
+
return f"{role} \u00b7 {task_label}" if task_label else role
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"SUBAGENT_TASK_LABEL_MAX_CHARS",
|
|
47
|
+
"subagent_identity",
|
|
48
|
+
"subagent_task_label",
|
|
49
|
+
]
|