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,403 @@
|
|
|
1
|
+
"""Bounded handling for a model endpoint that stops answering.
|
|
2
|
+
|
|
3
|
+
Some endpoints, under some contexts, start returning responses with no text and
|
|
4
|
+
no tool calls. Retrying the identical request does not help: the input that
|
|
5
|
+
produced the empty response is re-sent unchanged, so the endpoint produces
|
|
6
|
+
another empty response. Without a wall-clock bound the loop can run for hours
|
|
7
|
+
and then terminate as a failure even though the working tree already holds
|
|
8
|
+
finished work.
|
|
9
|
+
|
|
10
|
+
This module supplies the facts that bound that situation. It is pure and side
|
|
11
|
+
effect free; the clock is injectable so the time-based rules are testable
|
|
12
|
+
without sleeping.
|
|
13
|
+
|
|
14
|
+
* ``response_is_contentless`` — a response carrying neither text nor tool calls.
|
|
15
|
+
A response with tool calls and no text is ordinary and is never counted.
|
|
16
|
+
* ``EmptyResponseStallTracker`` — consecutive contentless responses, how long
|
|
17
|
+
the current contentless streak has lasted, how many recovery cycles the
|
|
18
|
+
session has spent, and the total time attributed to empty-response handling.
|
|
19
|
+
A stall is declared on *either* the count rule or the time rule, so a provider
|
|
20
|
+
that hangs for minutes per empty call is caught without waiting for the count.
|
|
21
|
+
* ``compact_recent_tool_output`` — drops the most recent tool-output block from
|
|
22
|
+
a message list so one recovery attempt re-issues against a different context
|
|
23
|
+
instead of the same one. Roles and ``tool_call_id`` values are preserved, so
|
|
24
|
+
the assistant/tool pairing every provider requires stays intact.
|
|
25
|
+
|
|
26
|
+
Nothing here inspects the task, the repository, or the model: the trigger is the
|
|
27
|
+
observed shape of provider responses.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import time
|
|
33
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
from ..branding import env_get
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Policy (kill-switch mirrors the route-arbitration / evidence-v2 idiom)
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
DEFAULT_STALL_THRESHOLD = 3
|
|
44
|
+
DEFAULT_STALL_SECONDS = 300.0
|
|
45
|
+
DEFAULT_MAX_RECOVERY_CYCLES = 2
|
|
46
|
+
DEFAULT_HANDLING_BUDGET_SECONDS = 600.0
|
|
47
|
+
|
|
48
|
+
# Backoff before a recovery re-issue. Small and bounded: the point is to let a
|
|
49
|
+
# transient provider condition clear, not to wait out an outage.
|
|
50
|
+
_BACKOFF_BASE_SECONDS = 2.0
|
|
51
|
+
_BACKOFF_MAX_SECONDS = 30.0
|
|
52
|
+
|
|
53
|
+
# Characters of each recent tool result kept when the block is compacted. Short
|
|
54
|
+
# results are below this and survive verbatim, so compaction only removes bulk.
|
|
55
|
+
DEFAULT_TOOL_OUTPUT_KEEP_CHARS = 200
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def empty_response_stall_guard_enabled(cfg: Any | None) -> bool:
|
|
59
|
+
"""Kill-switch for bounded empty-response handling.
|
|
60
|
+
|
|
61
|
+
``ALYSIS_EMPTY_RESPONSE_STALL`` (off/0/false/no/disabled) wins over the
|
|
62
|
+
config value; default is on. When off, no stall is ever declared and the
|
|
63
|
+
legacy retry-then-terminate behaviour applies.
|
|
64
|
+
"""
|
|
65
|
+
env_value = env_get("ALYSIS_EMPTY_RESPONSE_STALL")
|
|
66
|
+
if env_value is not None:
|
|
67
|
+
normalized = str(env_value).strip().lower()
|
|
68
|
+
if normalized in {"off", "0", "false", "no", "disabled"}:
|
|
69
|
+
return False
|
|
70
|
+
if normalized in {"on", "1", "true", "yes", "enabled"}:
|
|
71
|
+
return True
|
|
72
|
+
return bool(getattr(cfg, "empty_response_stall_guard_enabled", True))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _positive_int(value: Any, fallback: int) -> int:
|
|
76
|
+
try:
|
|
77
|
+
parsed = int(value)
|
|
78
|
+
except (TypeError, ValueError):
|
|
79
|
+
return fallback
|
|
80
|
+
return parsed if parsed > 0 else fallback
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _positive_float(value: Any, fallback: float) -> float:
|
|
84
|
+
try:
|
|
85
|
+
parsed = float(value)
|
|
86
|
+
except (TypeError, ValueError):
|
|
87
|
+
return fallback
|
|
88
|
+
if parsed <= 0 or parsed != parsed or parsed in {float("inf"), float("-inf")}:
|
|
89
|
+
return fallback
|
|
90
|
+
return parsed
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class EmptyResponseStallPolicy:
|
|
95
|
+
"""Resolved thresholds for one session."""
|
|
96
|
+
|
|
97
|
+
enabled: bool = True
|
|
98
|
+
consecutive_threshold: int = DEFAULT_STALL_THRESHOLD
|
|
99
|
+
stall_seconds: float = DEFAULT_STALL_SECONDS
|
|
100
|
+
max_recovery_cycles: int = DEFAULT_MAX_RECOVERY_CYCLES
|
|
101
|
+
handling_budget_seconds: float = DEFAULT_HANDLING_BUDGET_SECONDS
|
|
102
|
+
|
|
103
|
+
def as_payload(self) -> dict[str, Any]:
|
|
104
|
+
return {
|
|
105
|
+
"enabled": self.enabled,
|
|
106
|
+
"consecutive_threshold": self.consecutive_threshold,
|
|
107
|
+
"stall_seconds": self.stall_seconds,
|
|
108
|
+
"max_recovery_cycles": self.max_recovery_cycles,
|
|
109
|
+
"handling_budget_seconds": self.handling_budget_seconds,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def resolve_empty_response_stall_policy(cfg: Any | None) -> EmptyResponseStallPolicy:
|
|
114
|
+
"""Build the policy from config, falling back to defaults on bad values."""
|
|
115
|
+
return EmptyResponseStallPolicy(
|
|
116
|
+
enabled=empty_response_stall_guard_enabled(cfg),
|
|
117
|
+
consecutive_threshold=_positive_int(
|
|
118
|
+
getattr(cfg, "empty_response_stall_threshold", None),
|
|
119
|
+
DEFAULT_STALL_THRESHOLD,
|
|
120
|
+
),
|
|
121
|
+
stall_seconds=_positive_float(
|
|
122
|
+
getattr(cfg, "empty_response_stall_seconds", None),
|
|
123
|
+
DEFAULT_STALL_SECONDS,
|
|
124
|
+
),
|
|
125
|
+
max_recovery_cycles=_positive_int(
|
|
126
|
+
getattr(cfg, "empty_response_max_recovery_cycles", None),
|
|
127
|
+
DEFAULT_MAX_RECOVERY_CYCLES,
|
|
128
|
+
),
|
|
129
|
+
handling_budget_seconds=_positive_float(
|
|
130
|
+
getattr(cfg, "empty_response_handling_budget_seconds", None),
|
|
131
|
+
DEFAULT_HANDLING_BUDGET_SECONDS,
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Response classification
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def response_is_contentless(response: Any) -> bool:
|
|
142
|
+
"""True when a model response carries neither text nor a tool call.
|
|
143
|
+
|
|
144
|
+
A response with tool calls but no text is the ordinary shape of a working
|
|
145
|
+
step and is never contentless. Reasoning traces are not content: a response
|
|
146
|
+
that only thought is still one the runtime cannot act on.
|
|
147
|
+
"""
|
|
148
|
+
if response is None:
|
|
149
|
+
return True
|
|
150
|
+
if getattr(response, "tool_calls", None):
|
|
151
|
+
return False
|
|
152
|
+
return not str(getattr(response, "content", "") or "").strip()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# Stall tracking
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True)
|
|
161
|
+
class StallSignal:
|
|
162
|
+
"""Outcome of observing one model response."""
|
|
163
|
+
|
|
164
|
+
stalled: bool
|
|
165
|
+
trigger: str
|
|
166
|
+
consecutive_contentless: int
|
|
167
|
+
streak_seconds: float
|
|
168
|
+
|
|
169
|
+
def as_payload(self) -> dict[str, Any]:
|
|
170
|
+
return {
|
|
171
|
+
"stalled": self.stalled,
|
|
172
|
+
"trigger": self.trigger,
|
|
173
|
+
"consecutive_contentless": self.consecutive_contentless,
|
|
174
|
+
"streak_seconds": round(self.streak_seconds, 3),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass(frozen=True)
|
|
179
|
+
class RecoveryPlan:
|
|
180
|
+
"""Whether one more recovery cycle may be spent, and how long to back off."""
|
|
181
|
+
|
|
182
|
+
allowed: bool
|
|
183
|
+
reason: str
|
|
184
|
+
cycle: int
|
|
185
|
+
backoff_seconds: float
|
|
186
|
+
|
|
187
|
+
def as_payload(self) -> dict[str, Any]:
|
|
188
|
+
return {
|
|
189
|
+
"allowed": self.allowed,
|
|
190
|
+
"reason": self.reason,
|
|
191
|
+
"cycle": self.cycle,
|
|
192
|
+
"backoff_seconds": round(self.backoff_seconds, 3),
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass
|
|
197
|
+
class EmptyResponseStallTracker:
|
|
198
|
+
"""Per-session state for empty-response detection and its time budget."""
|
|
199
|
+
|
|
200
|
+
policy: EmptyResponseStallPolicy = field(default_factory=EmptyResponseStallPolicy)
|
|
201
|
+
clock: Callable[[], float] = time.monotonic
|
|
202
|
+
consecutive_contentless: int = 0
|
|
203
|
+
recovery_cycles_used: int = 0
|
|
204
|
+
handling_seconds: float = 0.0
|
|
205
|
+
total_contentless_responses: int = 0
|
|
206
|
+
_streak_started_at: float | None = None
|
|
207
|
+
_billed_through: float | None = None
|
|
208
|
+
|
|
209
|
+
def observe(self, *, contentless: bool) -> StallSignal:
|
|
210
|
+
"""Record one model response and report whether the turn has stalled."""
|
|
211
|
+
now = float(self.clock())
|
|
212
|
+
if not contentless:
|
|
213
|
+
self._bill(now)
|
|
214
|
+
self.consecutive_contentless = 0
|
|
215
|
+
self._streak_started_at = None
|
|
216
|
+
self._billed_through = None
|
|
217
|
+
return StallSignal(
|
|
218
|
+
stalled=False,
|
|
219
|
+
trigger="",
|
|
220
|
+
consecutive_contentless=0,
|
|
221
|
+
streak_seconds=0.0,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
self.total_contentless_responses += 1
|
|
225
|
+
if self._streak_started_at is None:
|
|
226
|
+
# Time before the first contentless response belongs to ordinary
|
|
227
|
+
# work (a long tool run, a slow but answering model), so billing
|
|
228
|
+
# for empty-response handling starts here, not earlier.
|
|
229
|
+
self._streak_started_at = now
|
|
230
|
+
self._billed_through = now
|
|
231
|
+
self.consecutive_contentless += 1
|
|
232
|
+
streak_seconds = max(0.0, now - self._streak_started_at)
|
|
233
|
+
|
|
234
|
+
if not self.policy.enabled:
|
|
235
|
+
return StallSignal(
|
|
236
|
+
stalled=False,
|
|
237
|
+
trigger="",
|
|
238
|
+
consecutive_contentless=self.consecutive_contentless,
|
|
239
|
+
streak_seconds=streak_seconds,
|
|
240
|
+
)
|
|
241
|
+
if self.consecutive_contentless >= self.policy.consecutive_threshold:
|
|
242
|
+
trigger = "consecutive_contentless_responses"
|
|
243
|
+
elif streak_seconds >= self.policy.stall_seconds:
|
|
244
|
+
trigger = "contentless_streak_duration"
|
|
245
|
+
else:
|
|
246
|
+
trigger = ""
|
|
247
|
+
return StallSignal(
|
|
248
|
+
stalled=bool(trigger),
|
|
249
|
+
trigger=trigger,
|
|
250
|
+
consecutive_contentless=self.consecutive_contentless,
|
|
251
|
+
streak_seconds=streak_seconds,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def plan_recovery(self) -> RecoveryPlan:
|
|
255
|
+
"""Decide whether one more compaction-recovery cycle may be spent."""
|
|
256
|
+
self._bill(float(self.clock()))
|
|
257
|
+
cycle = self.recovery_cycles_used + 1
|
|
258
|
+
if not self.policy.enabled:
|
|
259
|
+
return RecoveryPlan(
|
|
260
|
+
allowed=False,
|
|
261
|
+
reason="stall_guard_disabled",
|
|
262
|
+
cycle=cycle,
|
|
263
|
+
backoff_seconds=0.0,
|
|
264
|
+
)
|
|
265
|
+
if self.recovery_cycles_used >= self.policy.max_recovery_cycles:
|
|
266
|
+
return RecoveryPlan(
|
|
267
|
+
allowed=False,
|
|
268
|
+
reason="recovery_cycles_exhausted",
|
|
269
|
+
cycle=cycle,
|
|
270
|
+
backoff_seconds=0.0,
|
|
271
|
+
)
|
|
272
|
+
if self.handling_seconds >= self.policy.handling_budget_seconds:
|
|
273
|
+
return RecoveryPlan(
|
|
274
|
+
allowed=False,
|
|
275
|
+
reason="handling_budget_exhausted",
|
|
276
|
+
cycle=cycle,
|
|
277
|
+
backoff_seconds=0.0,
|
|
278
|
+
)
|
|
279
|
+
return RecoveryPlan(
|
|
280
|
+
allowed=True,
|
|
281
|
+
reason="recovery_available",
|
|
282
|
+
cycle=cycle,
|
|
283
|
+
backoff_seconds=self._backoff_seconds(cycle),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def note_recovery_started(self, *, backoff_seconds: float = 0.0) -> None:
|
|
287
|
+
"""Charge one recovery cycle (plus the backoff actually slept)."""
|
|
288
|
+
self.recovery_cycles_used += 1
|
|
289
|
+
self.handling_seconds += max(0.0, float(backoff_seconds))
|
|
290
|
+
self.consecutive_contentless = 0
|
|
291
|
+
self._streak_started_at = None
|
|
292
|
+
self._billed_through = None
|
|
293
|
+
|
|
294
|
+
def remaining_budget_seconds(self) -> float:
|
|
295
|
+
return max(0.0, self.policy.handling_budget_seconds - self.handling_seconds)
|
|
296
|
+
|
|
297
|
+
def as_payload(self) -> dict[str, Any]:
|
|
298
|
+
return {
|
|
299
|
+
"consecutive_contentless": self.consecutive_contentless,
|
|
300
|
+
"contentless_responses": self.total_contentless_responses,
|
|
301
|
+
"recovery_cycles_used": self.recovery_cycles_used,
|
|
302
|
+
"max_recovery_cycles": self.policy.max_recovery_cycles,
|
|
303
|
+
"handling_seconds": round(self.handling_seconds, 3),
|
|
304
|
+
"handling_budget_seconds": self.policy.handling_budget_seconds,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
def _bill(self, now: float) -> None:
|
|
308
|
+
if self._billed_through is None:
|
|
309
|
+
return
|
|
310
|
+
self.handling_seconds += max(0.0, now - self._billed_through)
|
|
311
|
+
self._billed_through = now
|
|
312
|
+
|
|
313
|
+
def _backoff_seconds(self, cycle: int) -> float:
|
|
314
|
+
exponent = max(0, cycle - 1)
|
|
315
|
+
return min(_BACKOFF_MAX_SECONDS, _BACKOFF_BASE_SECONDS * (2**exponent))
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
# Context compaction for the recovery re-issue
|
|
320
|
+
# ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@dataclass(frozen=True)
|
|
324
|
+
class ToolOutputCompaction:
|
|
325
|
+
"""What ``compact_recent_tool_output`` actually removed."""
|
|
326
|
+
|
|
327
|
+
applied: bool
|
|
328
|
+
compacted_messages: int
|
|
329
|
+
removed_characters: int
|
|
330
|
+
block_size: int
|
|
331
|
+
|
|
332
|
+
def as_payload(self) -> dict[str, Any]:
|
|
333
|
+
return {
|
|
334
|
+
"applied": self.applied,
|
|
335
|
+
"compacted_messages": self.compacted_messages,
|
|
336
|
+
"removed_characters": self.removed_characters,
|
|
337
|
+
"block_size": self.block_size,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _is_tool_message(message: Any) -> bool:
|
|
342
|
+
return isinstance(message, Mapping) and str(message.get("role") or "") == "tool"
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _elision_note(removed: int) -> str:
|
|
346
|
+
return (
|
|
347
|
+
f"\n[Tool output elided by the runtime after repeated empty model responses: "
|
|
348
|
+
f"{removed} characters removed. Re-run the tool if you still need this output.]"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def compact_recent_tool_output(
|
|
353
|
+
messages: Iterable[Mapping[str, Any]],
|
|
354
|
+
*,
|
|
355
|
+
keep_chars: int = DEFAULT_TOOL_OUTPUT_KEEP_CHARS,
|
|
356
|
+
) -> tuple[list[Any], ToolOutputCompaction]:
|
|
357
|
+
"""Drop the most recent tool-output block, keeping the message protocol valid.
|
|
358
|
+
|
|
359
|
+
The block is the trailing run of ``role="tool"`` messages, located by
|
|
360
|
+
scanning back from the end past any controller messages appended after it.
|
|
361
|
+
Each oversized result keeps its first ``keep_chars`` characters plus an
|
|
362
|
+
explicit note that the rest was removed, so the model is never told a
|
|
363
|
+
truncated result is complete. Roles and ``tool_call_id`` values are
|
|
364
|
+
untouched, so every assistant tool call keeps its matching result.
|
|
365
|
+
"""
|
|
366
|
+
original = [dict(message) if isinstance(message, Mapping) else message for message in messages]
|
|
367
|
+
keep = max(0, int(keep_chars))
|
|
368
|
+
|
|
369
|
+
last_tool_index: int | None = None
|
|
370
|
+
for index in range(len(original) - 1, -1, -1):
|
|
371
|
+
if _is_tool_message(original[index]):
|
|
372
|
+
last_tool_index = index
|
|
373
|
+
break
|
|
374
|
+
if last_tool_index is None:
|
|
375
|
+
return original, ToolOutputCompaction(
|
|
376
|
+
applied=False,
|
|
377
|
+
compacted_messages=0,
|
|
378
|
+
removed_characters=0,
|
|
379
|
+
block_size=0,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
start = last_tool_index
|
|
383
|
+
while start - 1 >= 0 and _is_tool_message(original[start - 1]):
|
|
384
|
+
start -= 1
|
|
385
|
+
|
|
386
|
+
compacted_messages = 0
|
|
387
|
+
removed_characters = 0
|
|
388
|
+
for index in range(start, last_tool_index + 1):
|
|
389
|
+
message = original[index]
|
|
390
|
+
content = str(message.get("content") or "")
|
|
391
|
+
if len(content) <= keep:
|
|
392
|
+
continue
|
|
393
|
+
removed = len(content) - keep
|
|
394
|
+
message["content"] = content[:keep] + _elision_note(removed)
|
|
395
|
+
compacted_messages += 1
|
|
396
|
+
removed_characters += removed
|
|
397
|
+
|
|
398
|
+
return original, ToolOutputCompaction(
|
|
399
|
+
applied=compacted_messages > 0,
|
|
400
|
+
compacted_messages=compacted_messages,
|
|
401
|
+
removed_characters=removed_characters,
|
|
402
|
+
block_size=last_tool_index - start + 1,
|
|
403
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgentRuntimeError(RuntimeError):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
*args: object,
|
|
10
|
+
result_payload: dict[str, Any] | None = None,
|
|
11
|
+
) -> None:
|
|
12
|
+
self.result_payload = result_payload
|
|
13
|
+
super().__init__(*args)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ApprovalDeclinedError(AgentRuntimeError):
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
approval_kind: str,
|
|
20
|
+
*,
|
|
21
|
+
message: str | None = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
self.approval_kind = str(approval_kind or "approval").strip() or "approval"
|
|
24
|
+
super().__init__(message or f"User declined: {self.approval_kind}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SessionWorkdirError(AgentRuntimeError):
|
|
28
|
+
pass
|