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,830 @@
|
|
|
1
|
+
# ruff: noqa: F821
|
|
2
|
+
# Dependencies are injected at runtime from alysis_code.cli to preserve monkeypatch surfaces.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..runtime_kind import RuntimeKind
|
|
8
|
+
from ..step_budget import normalize_step_budget_policy, step_budget_is_autonomous
|
|
9
|
+
from ..surface.styles import STYLE_CONTENT, STYLE_EMPHASIS
|
|
10
|
+
|
|
11
|
+
_PROTECTED_GLOBAL_NAMES: set[str] = set()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _sync_cli_globals(cli_mod: Any) -> None:
|
|
15
|
+
module_globals = globals()
|
|
16
|
+
if not _PROTECTED_GLOBAL_NAMES:
|
|
17
|
+
for local_name, local_value in module_globals.items():
|
|
18
|
+
if callable(local_value):
|
|
19
|
+
_PROTECTED_GLOBAL_NAMES.add(local_name)
|
|
20
|
+
for name, value in cli_mod.__dict__.items():
|
|
21
|
+
if name.startswith("__") or name in _PROTECTED_GLOBAL_NAMES:
|
|
22
|
+
continue
|
|
23
|
+
module_globals[name] = value
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _resume_positive_int(raw: object, *, default: int) -> int:
|
|
27
|
+
try:
|
|
28
|
+
value = int(raw) if raw is not None else default
|
|
29
|
+
except (TypeError, ValueError):
|
|
30
|
+
return default
|
|
31
|
+
return value if value > 0 else default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _resume_optional_positive_int(raw: object) -> int | None:
|
|
35
|
+
try:
|
|
36
|
+
value = int(raw) if raw is not None else None
|
|
37
|
+
except (TypeError, ValueError):
|
|
38
|
+
return None
|
|
39
|
+
if value is None or value <= 0:
|
|
40
|
+
return None
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _select_chat_resume_interactive(
|
|
45
|
+
*,
|
|
46
|
+
current_session_id: str,
|
|
47
|
+
sessions: list[SessionInfo],
|
|
48
|
+
console: Console,
|
|
49
|
+
) -> tuple[str | None, bool]:
|
|
50
|
+
if _is_non_interactive_terminal():
|
|
51
|
+
return None, False
|
|
52
|
+
if not sessions:
|
|
53
|
+
return None, True
|
|
54
|
+
try:
|
|
55
|
+
from prompt_toolkit.input import create_input
|
|
56
|
+
from prompt_toolkit.keys import Keys
|
|
57
|
+
except Exception:
|
|
58
|
+
return None, False
|
|
59
|
+
|
|
60
|
+
selected_index = 0
|
|
61
|
+
scroll_offset = 0
|
|
62
|
+
rename_mode = False
|
|
63
|
+
rename_buffer = ""
|
|
64
|
+
status_message = ""
|
|
65
|
+
display_rows = _chat_resume_display_rows(sessions=sessions)
|
|
66
|
+
|
|
67
|
+
def _selected_session_id() -> str | None:
|
|
68
|
+
if not sessions:
|
|
69
|
+
return None
|
|
70
|
+
if selected_index < 0 or selected_index >= len(sessions):
|
|
71
|
+
return None
|
|
72
|
+
return sessions[selected_index].session_id
|
|
73
|
+
|
|
74
|
+
def _page_size() -> int:
|
|
75
|
+
return max(1, _resume_visible_session_rows(has_status_message=bool(status_message)))
|
|
76
|
+
|
|
77
|
+
def _sync_viewport() -> None:
|
|
78
|
+
nonlocal scroll_offset
|
|
79
|
+
if not sessions:
|
|
80
|
+
scroll_offset = 0
|
|
81
|
+
return
|
|
82
|
+
if selected_index < 0:
|
|
83
|
+
selected_index_local = 0
|
|
84
|
+
elif selected_index >= len(sessions):
|
|
85
|
+
selected_index_local = len(sessions) - 1
|
|
86
|
+
else:
|
|
87
|
+
selected_index_local = selected_index
|
|
88
|
+
scroll_offset = _clamp_resume_scroll_offset(
|
|
89
|
+
total_rows=len(sessions),
|
|
90
|
+
selected_index=selected_index_local,
|
|
91
|
+
scroll_offset=scroll_offset,
|
|
92
|
+
visible_session_rows=_page_size(),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def _render_panel() -> Panel:
|
|
96
|
+
_sync_viewport()
|
|
97
|
+
selected_id = _selected_session_id()
|
|
98
|
+
return _chat_resume_panel(
|
|
99
|
+
current_session_id=current_session_id,
|
|
100
|
+
sessions=sessions,
|
|
101
|
+
selected_session_id=selected_id,
|
|
102
|
+
interactive=True,
|
|
103
|
+
display_rows=display_rows,
|
|
104
|
+
rename_session_id=selected_id if rename_mode else None,
|
|
105
|
+
rename_buffer=rename_buffer,
|
|
106
|
+
status_message=status_message or None,
|
|
107
|
+
scroll_offset=scroll_offset,
|
|
108
|
+
visible_session_rows=_page_size(),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
input_reader = create_input()
|
|
112
|
+
try:
|
|
113
|
+
with _watch_terminal_resize() as consume_resize:
|
|
114
|
+
with _Live(
|
|
115
|
+
_render_panel(),
|
|
116
|
+
console=console,
|
|
117
|
+
auto_refresh=False,
|
|
118
|
+
transient=False,
|
|
119
|
+
screen=True,
|
|
120
|
+
) as live:
|
|
121
|
+
with input_reader.raw_mode():
|
|
122
|
+
while True:
|
|
123
|
+
resized = consume_resize()
|
|
124
|
+
if resized:
|
|
125
|
+
_sync_viewport()
|
|
126
|
+
_clear_terminal_screen(console=console)
|
|
127
|
+
live.update(_render_panel(), refresh=True)
|
|
128
|
+
|
|
129
|
+
key_presses = _read_input_keys_with_timeout(
|
|
130
|
+
input_reader=input_reader,
|
|
131
|
+
timeout_s=0.12,
|
|
132
|
+
)
|
|
133
|
+
if not key_presses:
|
|
134
|
+
continue
|
|
135
|
+
panel_updated = False
|
|
136
|
+
for key_press in key_presses:
|
|
137
|
+
key = key_press.key
|
|
138
|
+
key_char = _resume_picker_key_char(key_press)
|
|
139
|
+
|
|
140
|
+
if _terminal_too_small():
|
|
141
|
+
if (
|
|
142
|
+
key == Keys.Escape
|
|
143
|
+
or key == Keys.ControlC
|
|
144
|
+
or key
|
|
145
|
+
in {
|
|
146
|
+
"escape",
|
|
147
|
+
"c-c",
|
|
148
|
+
}
|
|
149
|
+
):
|
|
150
|
+
return None, True
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
if rename_mode:
|
|
154
|
+
if key == Keys.Enter or key in {"enter", "\r", "\n"}:
|
|
155
|
+
if sessions and 0 <= selected_index < len(sessions):
|
|
156
|
+
renamed, status_message = (
|
|
157
|
+
_rename_resume_session_custom_title(
|
|
158
|
+
info=sessions[selected_index],
|
|
159
|
+
new_title=rename_buffer,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
if renamed:
|
|
163
|
+
display_rows = _chat_resume_display_rows(
|
|
164
|
+
sessions=sessions
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
status_message = "Rename canceled."
|
|
168
|
+
rename_mode = False
|
|
169
|
+
_sync_viewport()
|
|
170
|
+
panel_updated = True
|
|
171
|
+
continue
|
|
172
|
+
if (
|
|
173
|
+
key == Keys.Escape
|
|
174
|
+
or key == Keys.ControlC
|
|
175
|
+
or key
|
|
176
|
+
in {
|
|
177
|
+
"escape",
|
|
178
|
+
"c-c",
|
|
179
|
+
}
|
|
180
|
+
):
|
|
181
|
+
rename_mode = False
|
|
182
|
+
status_message = "Rename canceled."
|
|
183
|
+
_sync_viewport()
|
|
184
|
+
panel_updated = True
|
|
185
|
+
continue
|
|
186
|
+
if key == Keys.Backspace or key in {"backspace", "c-h"}:
|
|
187
|
+
if rename_buffer:
|
|
188
|
+
rename_buffer = rename_buffer[:-1]
|
|
189
|
+
panel_updated = True
|
|
190
|
+
continue
|
|
191
|
+
if key_char and key_char in {"\x7f", "\b"}:
|
|
192
|
+
if rename_buffer:
|
|
193
|
+
rename_buffer = rename_buffer[:-1]
|
|
194
|
+
panel_updated = True
|
|
195
|
+
continue
|
|
196
|
+
if key_char and key_char >= " ":
|
|
197
|
+
rename_buffer += key_char
|
|
198
|
+
panel_updated = True
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
if key == Keys.Up or key == "up":
|
|
202
|
+
if selected_index > 0:
|
|
203
|
+
selected_index -= 1
|
|
204
|
+
status_message = ""
|
|
205
|
+
_sync_viewport()
|
|
206
|
+
panel_updated = True
|
|
207
|
+
continue
|
|
208
|
+
if key == Keys.Down or key == "down":
|
|
209
|
+
if selected_index < len(sessions) - 1:
|
|
210
|
+
selected_index += 1
|
|
211
|
+
status_message = ""
|
|
212
|
+
_sync_viewport()
|
|
213
|
+
panel_updated = True
|
|
214
|
+
continue
|
|
215
|
+
if key == Keys.PageUp or key in {"pageup", "s-pageup"}:
|
|
216
|
+
selected_index = max(0, selected_index - _page_size())
|
|
217
|
+
status_message = ""
|
|
218
|
+
_sync_viewport()
|
|
219
|
+
panel_updated = True
|
|
220
|
+
continue
|
|
221
|
+
if key == Keys.PageDown or key in {"pagedown", "s-pagedown"}:
|
|
222
|
+
selected_index = min(
|
|
223
|
+
len(sessions) - 1, selected_index + _page_size()
|
|
224
|
+
)
|
|
225
|
+
status_message = ""
|
|
226
|
+
_sync_viewport()
|
|
227
|
+
panel_updated = True
|
|
228
|
+
continue
|
|
229
|
+
if key == Keys.Home or key == "home":
|
|
230
|
+
selected_index = 0
|
|
231
|
+
status_message = ""
|
|
232
|
+
_sync_viewport()
|
|
233
|
+
panel_updated = True
|
|
234
|
+
continue
|
|
235
|
+
if key == Keys.End or key == "end":
|
|
236
|
+
selected_index = len(sessions) - 1
|
|
237
|
+
status_message = ""
|
|
238
|
+
_sync_viewport()
|
|
239
|
+
panel_updated = True
|
|
240
|
+
continue
|
|
241
|
+
if isinstance(key, str) and key.isdigit():
|
|
242
|
+
idx = int(key) - 1
|
|
243
|
+
if 0 <= idx < len(sessions):
|
|
244
|
+
selected_index = idx
|
|
245
|
+
status_message = ""
|
|
246
|
+
_sync_viewport()
|
|
247
|
+
panel_updated = True
|
|
248
|
+
continue
|
|
249
|
+
if key == Keys.Enter or key in {"enter", "\r", "\n"}:
|
|
250
|
+
if not sessions:
|
|
251
|
+
return None, True
|
|
252
|
+
return sessions[selected_index].session_id, True
|
|
253
|
+
if (
|
|
254
|
+
key == Keys.Escape
|
|
255
|
+
or key == Keys.ControlC
|
|
256
|
+
or key
|
|
257
|
+
in {
|
|
258
|
+
"escape",
|
|
259
|
+
"c-c",
|
|
260
|
+
}
|
|
261
|
+
):
|
|
262
|
+
return None, True
|
|
263
|
+
if key_char and key_char.lower() == "r":
|
|
264
|
+
selected_id = _selected_session_id()
|
|
265
|
+
if selected_id is None:
|
|
266
|
+
continue
|
|
267
|
+
current_row = next(
|
|
268
|
+
(
|
|
269
|
+
item
|
|
270
|
+
for item in display_rows
|
|
271
|
+
if item.session_id == selected_id
|
|
272
|
+
),
|
|
273
|
+
None,
|
|
274
|
+
)
|
|
275
|
+
rename_buffer = (
|
|
276
|
+
current_row.preview if current_row is not None else ""
|
|
277
|
+
)
|
|
278
|
+
rename_mode = True
|
|
279
|
+
status_message = ""
|
|
280
|
+
_sync_viewport()
|
|
281
|
+
panel_updated = True
|
|
282
|
+
continue
|
|
283
|
+
if key_char and key_char.lower() == "d":
|
|
284
|
+
if not sessions:
|
|
285
|
+
continue
|
|
286
|
+
deleted, status_message = _delete_resume_session(
|
|
287
|
+
info=sessions[selected_index]
|
|
288
|
+
)
|
|
289
|
+
if deleted:
|
|
290
|
+
sessions.pop(selected_index)
|
|
291
|
+
if not sessions:
|
|
292
|
+
return None, True
|
|
293
|
+
selected_index = min(selected_index, len(sessions) - 1)
|
|
294
|
+
display_rows = _chat_resume_display_rows(sessions=sessions)
|
|
295
|
+
_sync_viewport()
|
|
296
|
+
panel_updated = True
|
|
297
|
+
continue
|
|
298
|
+
if panel_updated:
|
|
299
|
+
live.update(_render_panel(), refresh=True)
|
|
300
|
+
except Exception as e: # noqa: BLE001
|
|
301
|
+
console.print(f"[yellow]Resume picker unavailable:[/yellow] {e}")
|
|
302
|
+
return None, False
|
|
303
|
+
finally:
|
|
304
|
+
close = getattr(input_reader, "close", None)
|
|
305
|
+
if callable(close):
|
|
306
|
+
close()
|
|
307
|
+
return None, True
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _chat_resume_panel(
|
|
311
|
+
*,
|
|
312
|
+
current_session_id: str,
|
|
313
|
+
sessions: list[SessionInfo],
|
|
314
|
+
selected_session_id: str | None = None,
|
|
315
|
+
interactive: bool = False,
|
|
316
|
+
display_rows: list[_ResumeSessionRow] | None = None,
|
|
317
|
+
rename_session_id: str | None = None,
|
|
318
|
+
rename_buffer: str = "",
|
|
319
|
+
status_message: str | None = None,
|
|
320
|
+
scroll_offset: int = 0,
|
|
321
|
+
visible_session_rows: int | None = None,
|
|
322
|
+
) -> Panel:
|
|
323
|
+
from rich.align import Align
|
|
324
|
+
from rich.text import Text
|
|
325
|
+
|
|
326
|
+
if _terminal_too_small():
|
|
327
|
+
return _terminal_too_small_panel()
|
|
328
|
+
|
|
329
|
+
_ = interactive
|
|
330
|
+
current = current_session_id.strip() or "-"
|
|
331
|
+
selected = (selected_session_id or "").strip().casefold()
|
|
332
|
+
rename_target = (rename_session_id or "").strip().casefold()
|
|
333
|
+
rows = (
|
|
334
|
+
display_rows if display_rows is not None else _chat_resume_display_rows(sessions=sessions)
|
|
335
|
+
)
|
|
336
|
+
selected_index = _resolve_resume_selected_index(
|
|
337
|
+
rows=rows, selected_session_id=selected_session_id
|
|
338
|
+
)
|
|
339
|
+
visible_count = (
|
|
340
|
+
int(visible_session_rows)
|
|
341
|
+
if visible_session_rows is not None
|
|
342
|
+
else _resume_visible_session_rows(has_status_message=bool(status_message))
|
|
343
|
+
)
|
|
344
|
+
normalized_offset = _clamp_resume_scroll_offset(
|
|
345
|
+
total_rows=len(rows),
|
|
346
|
+
selected_index=selected_index,
|
|
347
|
+
scroll_offset=scroll_offset,
|
|
348
|
+
visible_session_rows=visible_count,
|
|
349
|
+
)
|
|
350
|
+
start = normalized_offset
|
|
351
|
+
end = min(len(rows), start + max(1, visible_count))
|
|
352
|
+
window_rows = rows[start:end]
|
|
353
|
+
hidden_above = start
|
|
354
|
+
hidden_below = max(0, len(rows) - end)
|
|
355
|
+
columns, _ = _terminal_dimensions()
|
|
356
|
+
|
|
357
|
+
header = _Panel(
|
|
358
|
+
Align.center(Text("Resume Session", style="bold cyan")),
|
|
359
|
+
border_style="cyan",
|
|
360
|
+
padding=(0, 1),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
current_info = Text()
|
|
364
|
+
current_info.append("Current Session: ", style=STYLE_EMPHASIS)
|
|
365
|
+
current_info.append("* ", style="bold yellow")
|
|
366
|
+
current_info.append(current, style=STYLE_EMPHASIS)
|
|
367
|
+
current_panel = _Panel(current_info, border_style="bright_blue", padding=(0, 1))
|
|
368
|
+
|
|
369
|
+
idx_width = 3 if columns < 72 else 4
|
|
370
|
+
when_width = 15
|
|
371
|
+
if columns >= 90:
|
|
372
|
+
when_width = 20
|
|
373
|
+
if columns >= 130:
|
|
374
|
+
when_width = 24
|
|
375
|
+
|
|
376
|
+
table = _Table(show_header=False, box=None, expand=True, padding=(0, 1), collapse_padding=True)
|
|
377
|
+
table.add_column("idx", justify="right", no_wrap=True, width=idx_width)
|
|
378
|
+
table.add_column("session", style=STYLE_CONTENT, no_wrap=False, ratio=6, overflow="ellipsis")
|
|
379
|
+
table.add_column("when", justify="right", no_wrap=True, width=when_width)
|
|
380
|
+
|
|
381
|
+
if hidden_above > 0:
|
|
382
|
+
suffix = "" if hidden_above == 1 else "s"
|
|
383
|
+
table.add_row("", Text(f"... {hidden_above} more session{suffix} above", style="dim"), "")
|
|
384
|
+
|
|
385
|
+
last_group = rows[start - 1].group_label if start > 0 else ""
|
|
386
|
+
if window_rows:
|
|
387
|
+
first_group = window_rows[0].group_label
|
|
388
|
+
if start > 0 and rows[start - 1].group_label == first_group:
|
|
389
|
+
table.add_row("", Text(first_group, style="bold magenta"), "")
|
|
390
|
+
last_group = first_group
|
|
391
|
+
|
|
392
|
+
for item in window_rows:
|
|
393
|
+
if item.group_label != last_group:
|
|
394
|
+
table.add_row("", Text(item.group_label, style="bold magenta"), "")
|
|
395
|
+
last_group = item.group_label
|
|
396
|
+
|
|
397
|
+
is_selected = bool(selected) and item.session_id.casefold() == selected
|
|
398
|
+
is_renaming = bool(rename_target) and item.session_id.casefold() == rename_target
|
|
399
|
+
if is_renaming:
|
|
400
|
+
row_style = "bold black on bright_yellow"
|
|
401
|
+
elif is_selected:
|
|
402
|
+
row_style = "bold black on bright_cyan"
|
|
403
|
+
else:
|
|
404
|
+
row_style = item.recency_style
|
|
405
|
+
marker = ">" if is_selected else " "
|
|
406
|
+
idx_text = Text(f"{item.index:>2}", style=row_style)
|
|
407
|
+
if is_renaming:
|
|
408
|
+
live_name = _truncate_preview(rename_buffer, max_chars=72)
|
|
409
|
+
label_text = Text(f"{marker} Rename: {live_name}", style=row_style)
|
|
410
|
+
else:
|
|
411
|
+
label_text = Text(f"{marker} {item.preview}", style=row_style)
|
|
412
|
+
when_style = (
|
|
413
|
+
row_style if is_selected else ("dim" if item.recency_style == "dim" else STYLE_CONTENT)
|
|
414
|
+
)
|
|
415
|
+
when_text = Text(item.when_label, style=when_style)
|
|
416
|
+
table.add_row(idx_text, label_text, when_text)
|
|
417
|
+
|
|
418
|
+
if hidden_below > 0:
|
|
419
|
+
suffix = "" if hidden_below == 1 else "s"
|
|
420
|
+
table.add_row("", Text(f"... {hidden_below} more session{suffix} below", style="dim"), "")
|
|
421
|
+
|
|
422
|
+
footer_text = (
|
|
423
|
+
"/ Navigate PgUp/PgDn Page Home/End Jump "
|
|
424
|
+
"Enter Select r Rename d Delete Esc Cancel"
|
|
425
|
+
)
|
|
426
|
+
if rename_target:
|
|
427
|
+
footer_text = "Type title Enter Save Esc Cancel"
|
|
428
|
+
|
|
429
|
+
footer = _Panel(
|
|
430
|
+
Align.center(
|
|
431
|
+
Text(
|
|
432
|
+
footer_text,
|
|
433
|
+
style="dim",
|
|
434
|
+
)
|
|
435
|
+
),
|
|
436
|
+
border_style="bright_black",
|
|
437
|
+
padding=(0, 1),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
content = _table_grid(expand=True)
|
|
441
|
+
content.add_row(header)
|
|
442
|
+
content.add_row("")
|
|
443
|
+
content.add_row(current_panel)
|
|
444
|
+
content.add_row("")
|
|
445
|
+
content.add_row(table)
|
|
446
|
+
if status_message:
|
|
447
|
+
content.add_row("")
|
|
448
|
+
content.add_row(Text(status_message, style="dim"))
|
|
449
|
+
content.add_row("")
|
|
450
|
+
content.add_row(footer)
|
|
451
|
+
|
|
452
|
+
return _Panel(content, border_style="cyan")
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _resume_chat_session(
|
|
456
|
+
*,
|
|
457
|
+
session: Any,
|
|
458
|
+
target_session_id: str,
|
|
459
|
+
) -> tuple[bool, str, list[dict[str, Any]]]:
|
|
460
|
+
raw_requested_id = str(target_session_id or "").strip()
|
|
461
|
+
if not raw_requested_id:
|
|
462
|
+
return False, "[red]Usage:[/red] /resume <index|session_id>", []
|
|
463
|
+
requested_id = _normalize_chat_resume_session_id(raw_requested_id)
|
|
464
|
+
if requested_id is None:
|
|
465
|
+
return False, f"[red]Invalid session id:[/red] {raw_requested_id}", []
|
|
466
|
+
|
|
467
|
+
cfg = getattr(session, "cfg", None)
|
|
468
|
+
if not isinstance(cfg, AppConfig):
|
|
469
|
+
return False, "[red]Resume unavailable:[/red] missing session config.", []
|
|
470
|
+
|
|
471
|
+
store = getattr(session, "store", None)
|
|
472
|
+
sessions_dir_raw = getattr(store, "sessions_dir", None)
|
|
473
|
+
sessions_dir = (
|
|
474
|
+
Path(sessions_dir_raw) if sessions_dir_raw is not None else resolve_sessions_dir(cfg)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
target_path = _resolve_chat_resume_session_path(
|
|
478
|
+
sessions_dir=sessions_dir,
|
|
479
|
+
session_id=requested_id,
|
|
480
|
+
)
|
|
481
|
+
if target_path is None:
|
|
482
|
+
return False, f"[red]Session not found:[/red] {requested_id}", []
|
|
483
|
+
|
|
484
|
+
current_session_id = str(getattr(store, "session_id", "") or "")
|
|
485
|
+
if current_session_id == requested_id:
|
|
486
|
+
return True, f"Session already active: {requested_id}", []
|
|
487
|
+
|
|
488
|
+
synthesized_tool_results: list[str] = []
|
|
489
|
+
history_messages = _load_chat_resume_messages(
|
|
490
|
+
target_path,
|
|
491
|
+
synthesized_tool_results=synthesized_tool_results,
|
|
492
|
+
)
|
|
493
|
+
resume_context_message = _build_chat_resume_context_message(target_path)
|
|
494
|
+
start_payload = _load_chat_resume_session_start(target_path)
|
|
495
|
+
runtime_settings = _load_chat_resume_runtime_settings(target_path)
|
|
496
|
+
active_workdir_relpath = _load_chat_resume_active_workdir_relpath(target_path)
|
|
497
|
+
|
|
498
|
+
current_mode = str(getattr(session, "mode", "review") or "review").strip().lower()
|
|
499
|
+
start_mode = str(start_payload.get("mode") or "").strip().lower()
|
|
500
|
+
mode = start_mode if start_mode in _CHAT_MODES else current_mode
|
|
501
|
+
if mode not in _CHAT_MODES:
|
|
502
|
+
mode = "review"
|
|
503
|
+
|
|
504
|
+
raw_yes = start_payload.get("yes")
|
|
505
|
+
if isinstance(raw_yes, bool):
|
|
506
|
+
yes = raw_yes
|
|
507
|
+
else:
|
|
508
|
+
yes = bool(getattr(session, "yes", False))
|
|
509
|
+
|
|
510
|
+
resume_cfg = clone_cfg(cfg)
|
|
511
|
+
|
|
512
|
+
start_step_budget_policy = str(start_payload.get("step_budget_policy") or "").strip().lower()
|
|
513
|
+
if start_step_budget_policy in {"autonomous", "limited", "adaptive", "fixed"}:
|
|
514
|
+
resume_cfg.step_budget_policy = normalize_step_budget_policy(start_step_budget_policy)
|
|
515
|
+
else:
|
|
516
|
+
resume_cfg.step_budget_policy = normalize_step_budget_policy(
|
|
517
|
+
getattr(resume_cfg, "step_budget_policy", "autonomous")
|
|
518
|
+
)
|
|
519
|
+
stored_task_max_steps = _resume_optional_positive_int(start_payload.get("task_max_steps"))
|
|
520
|
+
if stored_task_max_steps is not None:
|
|
521
|
+
resume_cfg.task_max_steps = stored_task_max_steps
|
|
522
|
+
stored_subagent_max_steps = _resume_optional_positive_int(
|
|
523
|
+
start_payload.get("subagent_max_steps")
|
|
524
|
+
)
|
|
525
|
+
if stored_subagent_max_steps is not None:
|
|
526
|
+
resume_cfg.subagent_max_steps = stored_subagent_max_steps
|
|
527
|
+
|
|
528
|
+
default_max_steps = _resume_positive_int(
|
|
529
|
+
getattr(resume_cfg, "max_steps", DEFAULT_CHAT_MAX_STEPS),
|
|
530
|
+
default=DEFAULT_CHAT_MAX_STEPS,
|
|
531
|
+
)
|
|
532
|
+
max_steps = _resume_positive_int(start_payload.get("max_steps"), default=default_max_steps)
|
|
533
|
+
resume_cfg.max_steps = max_steps
|
|
534
|
+
|
|
535
|
+
raw_enable_chat_turn_step_budget = start_payload.get("enable_chat_turn_step_budget")
|
|
536
|
+
enable_chat_turn_step_budget = (
|
|
537
|
+
True
|
|
538
|
+
if step_budget_is_autonomous(resume_cfg.step_budget_policy)
|
|
539
|
+
else (
|
|
540
|
+
raw_enable_chat_turn_step_budget
|
|
541
|
+
if isinstance(raw_enable_chat_turn_step_budget, bool)
|
|
542
|
+
else True
|
|
543
|
+
)
|
|
544
|
+
)
|
|
545
|
+
chat_turn_fixed_override = _resume_optional_positive_int(
|
|
546
|
+
start_payload.get("chat_turn_fixed_override")
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
api_key_override = str(getattr(getattr(session, "client", None), "api_key", "") or "").strip()
|
|
550
|
+
if not api_key_override:
|
|
551
|
+
api_key_override = None
|
|
552
|
+
|
|
553
|
+
root = Path(getattr(session, "root", Path(".")))
|
|
554
|
+
if active_workdir_relpath:
|
|
555
|
+
try:
|
|
556
|
+
_resolve_chat_resume_active_workdir_path(
|
|
557
|
+
workspace_root=root,
|
|
558
|
+
active_workdir_relpath=active_workdir_relpath,
|
|
559
|
+
)
|
|
560
|
+
except Exception as e: # noqa: BLE001
|
|
561
|
+
return (
|
|
562
|
+
False,
|
|
563
|
+
"[red]Failed to resume session:[/red] "
|
|
564
|
+
f"could not restore active workdir {active_workdir_relpath!r}: {e}",
|
|
565
|
+
[],
|
|
566
|
+
)
|
|
567
|
+
no_log = not bool(getattr(store, "enabled", False))
|
|
568
|
+
usage_role = str(getattr(session, "usage_role", "main") or "main")
|
|
569
|
+
usage_hud_enabled = _chat_usage_hud_enabled(session)
|
|
570
|
+
|
|
571
|
+
start_model = str(start_payload.get("model") or "").strip()
|
|
572
|
+
model_restore_reason = "no historical model recorded"
|
|
573
|
+
historical_model_restored = False
|
|
574
|
+
try:
|
|
575
|
+
from ..llm.metadata import endpoint_descriptor_matches
|
|
576
|
+
from ..profiles import get_active_profile, resolve_effective_base_url
|
|
577
|
+
|
|
578
|
+
current_profile = get_active_profile(resume_cfg)
|
|
579
|
+
current_provider_base_url = resolve_effective_base_url(
|
|
580
|
+
cfg=resume_cfg,
|
|
581
|
+
profile=current_profile,
|
|
582
|
+
).rstrip("/")
|
|
583
|
+
connection_identity_keys = (
|
|
584
|
+
"profile_name",
|
|
585
|
+
"protocol",
|
|
586
|
+
"auth_provider",
|
|
587
|
+
"reasoning_trace_adapter",
|
|
588
|
+
)
|
|
589
|
+
has_connection_identity = all(key in start_payload for key in connection_identity_keys)
|
|
590
|
+
if "provider_base_url_descriptor" in start_payload:
|
|
591
|
+
endpoint_matches = endpoint_descriptor_matches(
|
|
592
|
+
current_provider_base_url,
|
|
593
|
+
start_payload.get("provider_base_url_descriptor"),
|
|
594
|
+
)
|
|
595
|
+
has_connection_identity = has_connection_identity and isinstance(
|
|
596
|
+
start_payload.get("provider_base_url_descriptor"), dict
|
|
597
|
+
)
|
|
598
|
+
else:
|
|
599
|
+
# Legacy sessions only have a raw URL. It may contain credentials
|
|
600
|
+
# and is not a trustworthy resume identity, so model restoration
|
|
601
|
+
# and provider continuation both fail closed.
|
|
602
|
+
has_connection_identity = False
|
|
603
|
+
endpoint_matches = False
|
|
604
|
+
connection_matches = has_connection_identity and (
|
|
605
|
+
str(start_payload.get("profile_name") or "") == current_profile.name
|
|
606
|
+
and str(start_payload.get("protocol") or "") == current_profile.protocol
|
|
607
|
+
and endpoint_matches
|
|
608
|
+
and str(start_payload.get("auth_provider") or "")
|
|
609
|
+
== str(current_profile.auth_provider or "")
|
|
610
|
+
and str(start_payload.get("reasoning_trace_adapter") or "auto")
|
|
611
|
+
== current_profile.reasoning_trace_adapter
|
|
612
|
+
)
|
|
613
|
+
except Exception: # noqa: BLE001 - legacy/corrupt identity must fail closed
|
|
614
|
+
has_connection_identity = False
|
|
615
|
+
connection_matches = False
|
|
616
|
+
|
|
617
|
+
if start_model and connection_matches:
|
|
618
|
+
resume_cfg.model = start_model
|
|
619
|
+
historical_model_restored = True
|
|
620
|
+
model_restore_reason = "stored provider connection matches the active connection"
|
|
621
|
+
elif start_model and has_connection_identity:
|
|
622
|
+
model_restore_reason = (
|
|
623
|
+
"stored provider connection differs from the active connection; using the current model"
|
|
624
|
+
)
|
|
625
|
+
elif start_model:
|
|
626
|
+
model_restore_reason = (
|
|
627
|
+
"legacy session has no verified provider identity; using the current model"
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
raw_temp = start_payload.get("temperature")
|
|
631
|
+
if raw_temp is not None:
|
|
632
|
+
try:
|
|
633
|
+
temp_value = float(raw_temp)
|
|
634
|
+
except (TypeError, ValueError):
|
|
635
|
+
temp_value = None
|
|
636
|
+
if temp_value is not None and temp_value >= 0:
|
|
637
|
+
_apply_temperature_override(resume_cfg, temp_value)
|
|
638
|
+
|
|
639
|
+
for key in (
|
|
640
|
+
"coding_temperature",
|
|
641
|
+
"review_temperature",
|
|
642
|
+
"planner_temperature",
|
|
643
|
+
"conflict_review_temperature",
|
|
644
|
+
"compactor_temperature",
|
|
645
|
+
"chat_temperature",
|
|
646
|
+
):
|
|
647
|
+
raw_value = start_payload.get(key)
|
|
648
|
+
if raw_value is None:
|
|
649
|
+
continue
|
|
650
|
+
try:
|
|
651
|
+
parsed_value = float(raw_value)
|
|
652
|
+
except (TypeError, ValueError):
|
|
653
|
+
continue
|
|
654
|
+
if parsed_value < 0:
|
|
655
|
+
continue
|
|
656
|
+
setattr(resume_cfg, key, parsed_value)
|
|
657
|
+
|
|
658
|
+
raw_stream = runtime_settings.get("stream")
|
|
659
|
+
if isinstance(raw_stream, bool):
|
|
660
|
+
resume_cfg.stream = raw_stream
|
|
661
|
+
|
|
662
|
+
start_routing_mode = str(start_payload.get("routing_mode") or "").strip().lower()
|
|
663
|
+
if start_routing_mode in {"auto", "code_only"}:
|
|
664
|
+
resume_cfg.routing_mode = start_routing_mode
|
|
665
|
+
|
|
666
|
+
start_compaction_enabled = start_payload.get("compaction_enabled")
|
|
667
|
+
resume_enable_compaction = (
|
|
668
|
+
bool(start_compaction_enabled)
|
|
669
|
+
if isinstance(start_compaction_enabled, bool)
|
|
670
|
+
else (
|
|
671
|
+
getattr(session, "conversation_compactor", None) is not None
|
|
672
|
+
or getattr(session, "tool_output_offloader", None) is not None
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
start_tool_output_offload = start_payload.get("tool_output_offload_enabled")
|
|
676
|
+
resume_enable_tool_output_offload = (
|
|
677
|
+
bool(start_tool_output_offload) if isinstance(start_tool_output_offload, bool) else None
|
|
678
|
+
)
|
|
679
|
+
start_conversation_summarization = start_payload.get("conversation_summarization_enabled")
|
|
680
|
+
resume_enable_conversation_summarization = (
|
|
681
|
+
bool(start_conversation_summarization)
|
|
682
|
+
if isinstance(start_conversation_summarization, bool)
|
|
683
|
+
else None
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
try:
|
|
687
|
+
new_session = create_session(
|
|
688
|
+
cfg=resume_cfg,
|
|
689
|
+
root=root,
|
|
690
|
+
mode=mode,
|
|
691
|
+
runtime_kind=RuntimeKind.INTERACTIVE_CHAT,
|
|
692
|
+
yes=yes,
|
|
693
|
+
max_steps=max_steps,
|
|
694
|
+
no_log=no_log,
|
|
695
|
+
api_key_override=api_key_override,
|
|
696
|
+
console=getattr(session, "console", None),
|
|
697
|
+
surface=getattr(session, "surface", None),
|
|
698
|
+
non_interactive=_is_non_interactive_terminal(),
|
|
699
|
+
enable_chat_turn_step_budget=enable_chat_turn_step_budget,
|
|
700
|
+
chat_turn_fixed_override=chat_turn_fixed_override,
|
|
701
|
+
session_log_dir_override=sessions_dir,
|
|
702
|
+
session_id_override=requested_id,
|
|
703
|
+
usage_role=usage_role,
|
|
704
|
+
enable_compaction=resume_enable_compaction,
|
|
705
|
+
enable_tool_output_offload=resume_enable_tool_output_offload,
|
|
706
|
+
enable_conversation_summarization=resume_enable_conversation_summarization,
|
|
707
|
+
active_workdir_relpath_override=active_workdir_relpath,
|
|
708
|
+
session_source="resume",
|
|
709
|
+
session_source_metadata={
|
|
710
|
+
"from_session_id": current_session_id,
|
|
711
|
+
"resumed_session_id": requested_id,
|
|
712
|
+
"loaded_message_count": len(history_messages),
|
|
713
|
+
"active_workdir_relpath": active_workdir_relpath,
|
|
714
|
+
},
|
|
715
|
+
)
|
|
716
|
+
except Exception as e: # noqa: BLE001
|
|
717
|
+
return False, f"[red]Failed to resume session:[/red] {e}", []
|
|
718
|
+
|
|
719
|
+
# Provider-owned continuation state is only valid on the exact route that
|
|
720
|
+
# produced it. Require both the session-level connection/model identity
|
|
721
|
+
# above and the per-message route stamp to match the newly created client.
|
|
722
|
+
# Legacy sessions, clients without a route identity, and any mismatch fail
|
|
723
|
+
# closed while retaining public message content and tool calls for ordinary
|
|
724
|
+
# full-history replay.
|
|
725
|
+
from ..llm.metadata import (
|
|
726
|
+
ProviderRouteIdentity,
|
|
727
|
+
gate_messages_for_provider_route,
|
|
728
|
+
strip_provider_metadata_from_messages,
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
active_route_identity = getattr(getattr(new_session, "client", None), "route_identity", None)
|
|
732
|
+
if historical_model_restored and isinstance(active_route_identity, ProviderRouteIdentity):
|
|
733
|
+
history_messages = gate_messages_for_provider_route(
|
|
734
|
+
history_messages,
|
|
735
|
+
active_route_identity,
|
|
736
|
+
)
|
|
737
|
+
else:
|
|
738
|
+
history_messages = strip_provider_metadata_from_messages(history_messages)
|
|
739
|
+
|
|
740
|
+
restored_trace_level = str(runtime_settings.get("trace_level") or "compact")
|
|
741
|
+
trace_setter = getattr(getattr(new_session, "surface", None), "set_trace_level", None)
|
|
742
|
+
if callable(trace_setter):
|
|
743
|
+
try:
|
|
744
|
+
trace_setter(restored_trace_level)
|
|
745
|
+
except Exception:
|
|
746
|
+
pass
|
|
747
|
+
|
|
748
|
+
combined_resume_context = resume_context_message or ""
|
|
749
|
+
resume_context_loaded = _insert_chat_resume_context_message(
|
|
750
|
+
new_session,
|
|
751
|
+
combined_resume_context,
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
if history_messages:
|
|
755
|
+
new_session.messages.extend(history_messages)
|
|
756
|
+
|
|
757
|
+
# Restored compaction state (summary/pins) only becomes model-visible via
|
|
758
|
+
# _upsert_context_messages, which otherwise runs on the next compaction.
|
|
759
|
+
# Re-inject here, after the pinned resume context bumped pinned_prefix_len,
|
|
760
|
+
# so the memory message lands after the pinned prefix.
|
|
761
|
+
compaction_memory_reinjected = False
|
|
762
|
+
resume_compactor = getattr(new_session, "conversation_compactor", None)
|
|
763
|
+
if resume_compactor is not None:
|
|
764
|
+
new_session.messages[:] = resume_compactor.reinject_context_messages(new_session.messages)
|
|
765
|
+
compaction_memory_reinjected = (
|
|
766
|
+
getattr(getattr(resume_compactor, "state", None), "memory_message_index", None)
|
|
767
|
+
is not None
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
new_session.store.append(
|
|
771
|
+
"system_note",
|
|
772
|
+
{
|
|
773
|
+
"message": "chat_resume",
|
|
774
|
+
"from_session_id": current_session_id,
|
|
775
|
+
"resumed_session_id": requested_id,
|
|
776
|
+
"loaded_messages": len(history_messages),
|
|
777
|
+
"synthesized_tool_results": len(synthesized_tool_results),
|
|
778
|
+
"synthesized_tool_result_ids": list(synthesized_tool_results),
|
|
779
|
+
"resume_context_loaded": resume_context_loaded,
|
|
780
|
+
"resume_context_chars": len(combined_resume_context),
|
|
781
|
+
"compaction_memory_reinjected": compaction_memory_reinjected,
|
|
782
|
+
"historical_model_restored": historical_model_restored,
|
|
783
|
+
"model_restore_reason": model_restore_reason,
|
|
784
|
+
},
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
close_fn = getattr(session, "close", None)
|
|
788
|
+
close_warning = ""
|
|
789
|
+
if callable(close_fn):
|
|
790
|
+
try:
|
|
791
|
+
close_fn()
|
|
792
|
+
except Exception as e: # noqa: BLE001
|
|
793
|
+
close_warning = f"previous session close failed during resume: {e}"
|
|
794
|
+
new_session.store.append(
|
|
795
|
+
"system_note",
|
|
796
|
+
{
|
|
797
|
+
"message": "chat_resume_warning",
|
|
798
|
+
"warning": close_warning,
|
|
799
|
+
},
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
session.__dict__.clear()
|
|
803
|
+
session.__dict__.update(new_session.__dict__)
|
|
804
|
+
_set_chat_usage_hud_enabled(session, usage_hud_enabled)
|
|
805
|
+
_refresh_chat_hud_context_cache(session)
|
|
806
|
+
_ensure_session_summary_metadata(session=session, allow_model_summary=False)
|
|
807
|
+
|
|
808
|
+
turn_count = sum(1 for msg in history_messages if msg.get("role") == "user")
|
|
809
|
+
suffix = "" if turn_count == 1 else "s"
|
|
810
|
+
message = f"Resumed session: {requested_id} ({turn_count} turn{suffix} loaded)."
|
|
811
|
+
if start_model and not historical_model_restored:
|
|
812
|
+
message += " The current provider and model were kept for safety."
|
|
813
|
+
if close_warning:
|
|
814
|
+
message += f" [yellow]Warning:[/yellow] {close_warning}."
|
|
815
|
+
return (True, message, history_messages)
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
def _select_chat_resume_interactive_impl(cli_mod: Any, *args: Any, **kwargs: Any) -> Any:
|
|
819
|
+
_sync_cli_globals(cli_mod)
|
|
820
|
+
return _select_chat_resume_interactive(*args, **kwargs)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _chat_resume_panel_impl(cli_mod: Any, *args: Any, **kwargs: Any) -> Any:
|
|
824
|
+
_sync_cli_globals(cli_mod)
|
|
825
|
+
return _chat_resume_panel(*args, **kwargs)
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def _resume_chat_session_impl(cli_mod: Any, *args: Any, **kwargs: Any) -> Any:
|
|
829
|
+
_sync_cli_globals(cli_mod)
|
|
830
|
+
return _resume_chat_session(*args, **kwargs)
|