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,125 @@
|
|
|
1
|
+
"""Classify submissions made while an agent turn is running.
|
|
2
|
+
|
|
3
|
+
The policy is an allowlist. A command is safe mid-turn only when it does not
|
|
4
|
+
mutate state read by the running turn, start or replace a turn or session, or
|
|
5
|
+
tear down the application. New and unknown commands therefore block until they
|
|
6
|
+
are reviewed explicitly.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
EXIT_WORDS = frozenset({"/exit", "/quit", ":q", "exit", "quit"})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MidTurnAction(Enum):
|
|
17
|
+
"""How the TUI should route a submission made during a turn."""
|
|
18
|
+
|
|
19
|
+
MESSAGE = "message"
|
|
20
|
+
ALLOW = "allow"
|
|
21
|
+
DEFER = "defer"
|
|
22
|
+
BLOCK = "block"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_ALLOWED_ALWAYS = frozenset(
|
|
26
|
+
{
|
|
27
|
+
"/help",
|
|
28
|
+
"/",
|
|
29
|
+
"/status",
|
|
30
|
+
"/subagents",
|
|
31
|
+
"/pwd",
|
|
32
|
+
"/context",
|
|
33
|
+
"/ctx",
|
|
34
|
+
"/usage",
|
|
35
|
+
"/model-info",
|
|
36
|
+
"/trace",
|
|
37
|
+
"/toolbar",
|
|
38
|
+
"/images",
|
|
39
|
+
"/image",
|
|
40
|
+
"/paste-image",
|
|
41
|
+
"/clear-images",
|
|
42
|
+
"/terminals",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
_ALLOWED_BARE_ONLY = frozenset({"/skill"})
|
|
47
|
+
|
|
48
|
+
_DEFERRED = frozenset({"/config", "/mode", "/model", "/persona"})
|
|
49
|
+
|
|
50
|
+
_BLOCK_REASONS = {
|
|
51
|
+
"/clear": "clearing would discard the conversation this turn is writing to",
|
|
52
|
+
"/resume": "resuming replaces the session this turn is using",
|
|
53
|
+
"/compact": "compaction rewrites the history this turn is appending to",
|
|
54
|
+
"/plan": "plan mode changes the permissions this turn is using",
|
|
55
|
+
"/ask": "that starts a new turn",
|
|
56
|
+
"/forge": "Forge takes over the session",
|
|
57
|
+
":forge": "Forge takes over the session",
|
|
58
|
+
"/login": "signing in replaces the active session",
|
|
59
|
+
"/logout": "signing out replaces the active session",
|
|
60
|
+
"/stream": "streaming is read at each step and would disrupt live output",
|
|
61
|
+
"/report": "report generation requires a settled session",
|
|
62
|
+
"/feedback": "feedback generation requires a settled session",
|
|
63
|
+
"/assets": "the assets view requires a settled Forge session",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_GENERIC_BLOCK_REASON = "that command cannot run while a turn is in flight"
|
|
67
|
+
_ESCAPE_HATCH = "Esc to interrupt, or wait for the turn to finish."
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _command_token(text: str) -> str:
|
|
71
|
+
stripped = str(text or "").strip()
|
|
72
|
+
if not stripped:
|
|
73
|
+
return ""
|
|
74
|
+
return stripped.split(maxsplit=1)[0].lower()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_command(text: str) -> bool:
|
|
78
|
+
"""Return whether text should be routed as a command rather than prose."""
|
|
79
|
+
stripped = str(text or "").strip()
|
|
80
|
+
if not stripped:
|
|
81
|
+
return False
|
|
82
|
+
if stripped.lower() in EXIT_WORDS:
|
|
83
|
+
return True
|
|
84
|
+
return stripped.startswith(("/", ":"))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def classify_mid_turn(text: str) -> MidTurnAction:
|
|
88
|
+
"""Classify text submitted while an agent turn is running."""
|
|
89
|
+
stripped = str(text or "").strip()
|
|
90
|
+
if not is_command(stripped):
|
|
91
|
+
return MidTurnAction.MESSAGE
|
|
92
|
+
if stripped.lower() in EXIT_WORDS:
|
|
93
|
+
return MidTurnAction.BLOCK
|
|
94
|
+
|
|
95
|
+
token = _command_token(stripped)
|
|
96
|
+
has_argument = len(stripped.split(maxsplit=1)) > 1
|
|
97
|
+
if token in _ALLOWED_ALWAYS:
|
|
98
|
+
return MidTurnAction.ALLOW
|
|
99
|
+
if token in _ALLOWED_BARE_ONLY and not has_argument:
|
|
100
|
+
return MidTurnAction.ALLOW
|
|
101
|
+
if token in _DEFERRED:
|
|
102
|
+
return MidTurnAction.DEFER
|
|
103
|
+
return MidTurnAction.BLOCK
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def defer_message(text: str) -> str:
|
|
107
|
+
"""Explain that a state-changing command will run after this turn."""
|
|
108
|
+
token = _command_token(text) or "That command"
|
|
109
|
+
return f"{token} is staged and will apply when this turn finishes."
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def block_message(text: str) -> str:
|
|
113
|
+
"""Explain why a submission is blocked and name the escape hatch."""
|
|
114
|
+
stripped = str(text or "").strip()
|
|
115
|
+
token = _command_token(stripped)
|
|
116
|
+
|
|
117
|
+
if stripped.lower() in EXIT_WORDS:
|
|
118
|
+
return f"Exit is unavailable while a turn is running. {_ESCAPE_HATCH}"
|
|
119
|
+
if token in _ALLOWED_BARE_ONLY:
|
|
120
|
+
reason = f"{token} with arguments changes what this turn is using"
|
|
121
|
+
else:
|
|
122
|
+
reason = _BLOCK_REASONS.get(token, _GENERIC_BLOCK_REASON)
|
|
123
|
+
|
|
124
|
+
label = token or "That"
|
|
125
|
+
return f"{label} is unavailable right now: {reason}. {_ESCAPE_HATCH}"
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# ruff: noqa: F821
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ...surface.styles import STYLE_CHROME, STYLE_CONTENT, STYLE_EMPHASIS
|
|
9
|
+
|
|
10
|
+
_PROTECTED_RENDERING_GLOBAL_NAMES: set[str] = set()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _sync_rendering_globals(source_globals: dict[str, Any]) -> None:
|
|
14
|
+
module_globals = globals()
|
|
15
|
+
if not _PROTECTED_RENDERING_GLOBAL_NAMES:
|
|
16
|
+
for local_name, local_value in module_globals.items():
|
|
17
|
+
if callable(local_value):
|
|
18
|
+
_PROTECTED_RENDERING_GLOBAL_NAMES.add(local_name)
|
|
19
|
+
for name, value in source_globals.items():
|
|
20
|
+
if name.startswith("__") or name in _PROTECTED_RENDERING_GLOBAL_NAMES:
|
|
21
|
+
continue
|
|
22
|
+
module_globals[name] = value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _forge_plan_command_guidance_lines() -> tuple[str, ...]:
|
|
26
|
+
return (
|
|
27
|
+
"Chat Plan Mode is unavailable.",
|
|
28
|
+
"In Forge, use:",
|
|
29
|
+
"/back",
|
|
30
|
+
"Forge plan commands:",
|
|
31
|
+
"/show",
|
|
32
|
+
"/plan markdown",
|
|
33
|
+
"/plan edit",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _print_forge_plan_command_guidance(*, console: Any) -> None:
|
|
38
|
+
for line in _forge_plan_command_guidance_lines():
|
|
39
|
+
console.print(line)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _chat_skill_usage_lines() -> tuple[str, ...]:
|
|
43
|
+
return (
|
|
44
|
+
"[yellow]Usage:[/yellow] /skill",
|
|
45
|
+
" /skill <name>",
|
|
46
|
+
" /skill <name> <task>",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _artifact_display_ref(*, root: Path, store_obj: Any, artifact_path: Path | None) -> str:
|
|
51
|
+
if artifact_path is None:
|
|
52
|
+
return "-"
|
|
53
|
+
layout = getattr(store_obj, "session_artifact_layout", None)
|
|
54
|
+
display_fn = getattr(layout, "display_reference_for_path", None)
|
|
55
|
+
if callable(display_fn):
|
|
56
|
+
try:
|
|
57
|
+
return str(display_fn(artifact_path=artifact_path, workspace_root=root))
|
|
58
|
+
except Exception: # noqa: BLE001
|
|
59
|
+
pass
|
|
60
|
+
try:
|
|
61
|
+
return artifact_path.resolve().relative_to(root.resolve()).as_posix()
|
|
62
|
+
except ValueError:
|
|
63
|
+
return artifact_path.name
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _render_planner_reply(
|
|
67
|
+
*, console: Any, message: str, questions: list[str] | None = None
|
|
68
|
+
) -> None:
|
|
69
|
+
console.print("[bold]Planner:[/bold]")
|
|
70
|
+
console.print(message)
|
|
71
|
+
if questions:
|
|
72
|
+
console.print("[dim]Planner questions[/dim]")
|
|
73
|
+
for question in questions:
|
|
74
|
+
console.print(f"- {question}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _render_labeled_chat_message(*, console: Any, label: str, message: str) -> None:
|
|
78
|
+
clean = str(message or "").strip() or "(empty message)"
|
|
79
|
+
prefix = ""
|
|
80
|
+
if label and str(label).strip().lower() not in {"you", "user"}:
|
|
81
|
+
prefix = f"{str(label).strip()} · "
|
|
82
|
+
console.print(
|
|
83
|
+
_forge_bar_text(
|
|
84
|
+
text=f"{prefix}{clean}",
|
|
85
|
+
style=STYLE_EMPHASIS,
|
|
86
|
+
bar_style=STYLE_EMPHASIS,
|
|
87
|
+
),
|
|
88
|
+
highlight=False,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _render_plan_draft(*, console: Any, draft: str) -> None:
|
|
93
|
+
from rich.console import Group
|
|
94
|
+
|
|
95
|
+
clean = str(draft or "").strip() or "(empty plan)"
|
|
96
|
+
lines = [line.rstrip() for line in clean.splitlines() if line.strip()]
|
|
97
|
+
numbered_line_count = sum(1 for line in lines if re.match(r"^\s*\d+[\.\)]\s+", line))
|
|
98
|
+
header = "Plan (draft)"
|
|
99
|
+
if numbered_line_count > 0:
|
|
100
|
+
suffix = "step" if numbered_line_count == 1 else "steps"
|
|
101
|
+
header = f"{header} {numbered_line_count} {suffix}"
|
|
102
|
+
|
|
103
|
+
renderables: list[Any] = [
|
|
104
|
+
_forge_bar_text(text=header, style=STYLE_EMPHASIS, bar_style=STYLE_CHROME)
|
|
105
|
+
]
|
|
106
|
+
if not lines:
|
|
107
|
+
lines = ["(empty plan)"]
|
|
108
|
+
for line in lines:
|
|
109
|
+
renderables.append(
|
|
110
|
+
_forge_bar_text(
|
|
111
|
+
text=f" {line}",
|
|
112
|
+
style=STYLE_CONTENT,
|
|
113
|
+
bar_style=STYLE_CHROME,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
console.print(Group(*renderables), highlight=False)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _render_chat_plan_mode_status(*, console: Console, plan_mode_state: Any) -> None:
|
|
120
|
+
if _chat_plan_mode_enabled(plan_mode_state):
|
|
121
|
+
restore_mode = _chat_plan_mode_restore_mode(plan_mode_state) or "readonly"
|
|
122
|
+
console.print(
|
|
123
|
+
"Plan Mode: on "
|
|
124
|
+
f"(persistent readonly planning overlay; /plan <task> stays the default draft/review/approve path; restores {_chat_mode_display(restore_mode)} on /plan off)"
|
|
125
|
+
)
|
|
126
|
+
latest_task = _chat_plan_mode_latest_task(plan_mode_state)
|
|
127
|
+
latest_draft = _chat_plan_mode_latest_draft(plan_mode_state)
|
|
128
|
+
if latest_draft is None or latest_task is None:
|
|
129
|
+
console.print(
|
|
130
|
+
"Stored draft: none yet. Draft here with a normal chat message, or use /plan off then /plan <task> for the default execution path."
|
|
131
|
+
)
|
|
132
|
+
return
|
|
133
|
+
console.print(f"Stored task: {_chat_plan_task_preview(latest_task)}")
|
|
134
|
+
if restore_mode == "readonly":
|
|
135
|
+
console.print(
|
|
136
|
+
"Stored draft: captured, but exact /plan approve cannot execute because this overlay started from Read-Only mode."
|
|
137
|
+
)
|
|
138
|
+
return
|
|
139
|
+
console.print(
|
|
140
|
+
f"Stored draft: ready for exact /plan approve (leaves readonly planning, restores {_chat_mode_display(restore_mode)}, and executes)."
|
|
141
|
+
)
|
|
142
|
+
return
|
|
143
|
+
console.print("Plan Mode: off")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _print_chat_context(*, console: Console, session: Any) -> None:
|
|
147
|
+
context_fn = getattr(session, "context_left", None)
|
|
148
|
+
if not callable(context_fn):
|
|
149
|
+
console.print("Context tracking unavailable for this session.")
|
|
150
|
+
return
|
|
151
|
+
_refresh_chat_hud_context_cache(session)
|
|
152
|
+
ctx = getattr(session, "_hud_context_cache", None)
|
|
153
|
+
if ctx is None:
|
|
154
|
+
console.print("Context tracking unavailable for this session.")
|
|
155
|
+
return
|
|
156
|
+
context_window_tokens = getattr(
|
|
157
|
+
ctx,
|
|
158
|
+
"context_window_tokens",
|
|
159
|
+
getattr(ctx, "max_input_tokens", "n/a"),
|
|
160
|
+
)
|
|
161
|
+
context_window_remaining = getattr(
|
|
162
|
+
ctx,
|
|
163
|
+
"context_window_remaining_tokens",
|
|
164
|
+
getattr(ctx, "remaining_tokens", "n/a"),
|
|
165
|
+
)
|
|
166
|
+
context_window_percent = getattr(
|
|
167
|
+
ctx,
|
|
168
|
+
"context_window_percent_left",
|
|
169
|
+
getattr(ctx, "percent_left", None),
|
|
170
|
+
)
|
|
171
|
+
table = _Table(title="Context Window Left")
|
|
172
|
+
table.add_column("field")
|
|
173
|
+
table.add_column("value")
|
|
174
|
+
table.add_row("model", str(getattr(ctx, "model_name", "-")))
|
|
175
|
+
table.add_row("model_metadata_source", str(getattr(ctx, "source", "-")))
|
|
176
|
+
table.add_row(
|
|
177
|
+
"capacity_provider_key",
|
|
178
|
+
str(getattr(ctx, "capacity_provider_key", None) or "-"),
|
|
179
|
+
)
|
|
180
|
+
table.add_row(
|
|
181
|
+
"context_window_source",
|
|
182
|
+
str(getattr(ctx, "context_window_source", None) or "-"),
|
|
183
|
+
)
|
|
184
|
+
table.add_row(
|
|
185
|
+
"max_output_source",
|
|
186
|
+
str(getattr(ctx, "max_output_source", None) or "-"),
|
|
187
|
+
)
|
|
188
|
+
table.add_row("token_count_source", str(getattr(ctx, "token_count_source", "-")))
|
|
189
|
+
table.add_row(
|
|
190
|
+
"token_count_confidence",
|
|
191
|
+
str(getattr(ctx, "token_count_confidence", "-")),
|
|
192
|
+
)
|
|
193
|
+
anchor_source = getattr(ctx, "anchor_token_count_source", None)
|
|
194
|
+
if anchor_source:
|
|
195
|
+
table.add_row("request_anchor_source", str(anchor_source))
|
|
196
|
+
table.add_row(
|
|
197
|
+
"request_anchor_confidence",
|
|
198
|
+
str(getattr(ctx, "anchor_token_count_confidence", "-")),
|
|
199
|
+
)
|
|
200
|
+
table.add_row("context_window_tokens", str(context_window_tokens))
|
|
201
|
+
table.add_row(
|
|
202
|
+
"projected_total_request_tokens",
|
|
203
|
+
str(getattr(ctx, "used_input_tokens", "n/a")),
|
|
204
|
+
)
|
|
205
|
+
table.add_row(
|
|
206
|
+
"local_request_estimate_tokens",
|
|
207
|
+
str(getattr(ctx, "local_request_estimate_tokens", "n/a")),
|
|
208
|
+
)
|
|
209
|
+
table.add_row("context_window_left_tokens", str(context_window_remaining))
|
|
210
|
+
table.add_row(
|
|
211
|
+
"context_window_left_percent",
|
|
212
|
+
f"{float(context_window_percent):.1f}%" if context_window_percent is not None else "n/a",
|
|
213
|
+
)
|
|
214
|
+
console.print(table)
|
|
215
|
+
|
|
216
|
+
messages_obj = getattr(session, "messages", [])
|
|
217
|
+
messages = messages_obj if isinstance(messages_obj, list) else []
|
|
218
|
+
tool_list_obj = getattr(session, "tool_list", None)
|
|
219
|
+
tool_list = tool_list_obj if isinstance(tool_list_obj, list) else None
|
|
220
|
+
compactor = getattr(session, "conversation_compactor", None)
|
|
221
|
+
registry = getattr(session, "model_registry", None)
|
|
222
|
+
model_name = str(getattr(getattr(session, "client", None), "model", "") or "").strip()
|
|
223
|
+
if not model_name:
|
|
224
|
+
model_name = str(getattr(ctx, "model_name", "") or "").strip()
|
|
225
|
+
|
|
226
|
+
cfg = getattr(session, "cfg", None)
|
|
227
|
+
if isinstance(cfg, AppConfig):
|
|
228
|
+
compaction_settings = _resolve_compaction_settings(cfg)
|
|
229
|
+
else:
|
|
230
|
+
compaction_settings = _resolve_compaction_settings(AppConfig())
|
|
231
|
+
|
|
232
|
+
model_meta = None
|
|
233
|
+
if registry is not None and model_name:
|
|
234
|
+
try:
|
|
235
|
+
model_meta = registry.get(model_name)
|
|
236
|
+
except Exception: # noqa: BLE001
|
|
237
|
+
model_meta = None
|
|
238
|
+
|
|
239
|
+
model_context_window_tokens = getattr(model_meta, "context_window_tokens", None)
|
|
240
|
+
max_output_tokens = getattr(model_meta, "max_output_tokens", None)
|
|
241
|
+
safety_margin_tokens = compaction_settings.safety_margin_tokens
|
|
242
|
+
effective_input_budget = (
|
|
243
|
+
getattr(ctx, "effective_input_budget", None)
|
|
244
|
+
or compute_input_budget(model_meta, safety_margin=safety_margin_tokens)
|
|
245
|
+
if model_meta is not None
|
|
246
|
+
else None
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
pinned_prefix_len = int(getattr(session, "pinned_prefix_len", 0) or 0)
|
|
250
|
+
if pinned_prefix_len <= 0 and compactor is not None and hasattr(compactor, "state"):
|
|
251
|
+
pinned_prefix_len = int(getattr(compactor.state, "pinned_prefix_len", 0) or 0)
|
|
252
|
+
if pinned_prefix_len <= 0:
|
|
253
|
+
for idx, msg in enumerate(messages):
|
|
254
|
+
if str(msg.get("role") or "") != "user":
|
|
255
|
+
continue
|
|
256
|
+
content = msg.get("content")
|
|
257
|
+
if isinstance(content, str) and content.startswith("<environment_context>"):
|
|
258
|
+
pinned_prefix_len = idx + 1
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
request_breakdown = estimate_request_token_breakdown(
|
|
262
|
+
messages=messages,
|
|
263
|
+
tool_list=tool_list,
|
|
264
|
+
pinned_prefix_len=pinned_prefix_len,
|
|
265
|
+
)
|
|
266
|
+
try:
|
|
267
|
+
total_request_tokens = max(0, int(getattr(ctx, "used_input_tokens", None)))
|
|
268
|
+
except (TypeError, ValueError):
|
|
269
|
+
total_request_tokens = request_breakdown.total_tokens
|
|
270
|
+
tokens_tools = max(0, int(getattr(request_breakdown, "tool_schema_tokens", 0) or 0))
|
|
271
|
+
if tokens_tools > total_request_tokens:
|
|
272
|
+
tokens_tools = total_request_tokens
|
|
273
|
+
tokens_messages = max(0, total_request_tokens - tokens_tools)
|
|
274
|
+
remaining_to_budget = getattr(ctx, "effective_remaining_tokens", None)
|
|
275
|
+
if remaining_to_budget is None and effective_input_budget is not None:
|
|
276
|
+
remaining_to_budget = max(0, effective_input_budget - total_request_tokens)
|
|
277
|
+
percent_budget_left = getattr(ctx, "effective_percent_left", None)
|
|
278
|
+
if (
|
|
279
|
+
percent_budget_left is None
|
|
280
|
+
and effective_input_budget is not None
|
|
281
|
+
and effective_input_budget > 0
|
|
282
|
+
and remaining_to_budget is not None
|
|
283
|
+
):
|
|
284
|
+
percent_budget_left = (remaining_to_budget / effective_input_budget) * 100.0
|
|
285
|
+
|
|
286
|
+
budget_table = _Table(title="Effective Input Budget")
|
|
287
|
+
budget_table.add_column("field")
|
|
288
|
+
budget_table.add_column("value")
|
|
289
|
+
budget_table.add_row("context_window_tokens", str(model_context_window_tokens or "n/a"))
|
|
290
|
+
budget_table.add_row("max_output_tokens", str(max_output_tokens or "n/a"))
|
|
291
|
+
budget_table.add_row("safety_margin_tokens", str(safety_margin_tokens))
|
|
292
|
+
budget_table.add_row(
|
|
293
|
+
"effective_input_budget",
|
|
294
|
+
str(effective_input_budget) if effective_input_budget is not None else "n/a",
|
|
295
|
+
)
|
|
296
|
+
budget_table.add_row("estimated_messages_tokens", str(tokens_messages))
|
|
297
|
+
budget_table.add_row("estimated_tools_tokens", str(tokens_tools))
|
|
298
|
+
budget_table.add_row("estimated_total_request_tokens", str(total_request_tokens))
|
|
299
|
+
budget_table.add_row(
|
|
300
|
+
"remaining_to_budget",
|
|
301
|
+
str(remaining_to_budget) if remaining_to_budget is not None else "n/a",
|
|
302
|
+
)
|
|
303
|
+
budget_table.add_row(
|
|
304
|
+
"percent_budget_left",
|
|
305
|
+
f"{percent_budget_left:.1f}%" if percent_budget_left is not None else "n/a",
|
|
306
|
+
)
|
|
307
|
+
console.print(budget_table)
|
|
308
|
+
|
|
309
|
+
dynamic_percent_left = getattr(ctx, "dynamic_context_percent_left", None)
|
|
310
|
+
dynamic_table = _Table(title="Conversation Context Left")
|
|
311
|
+
dynamic_table.add_column("field")
|
|
312
|
+
dynamic_table.add_column("value")
|
|
313
|
+
dynamic_table.add_row(
|
|
314
|
+
"startup_baseline_tokens",
|
|
315
|
+
str(getattr(ctx, "startup_baseline_tokens", "n/a")),
|
|
316
|
+
)
|
|
317
|
+
dynamic_table.add_row(
|
|
318
|
+
"dynamic_context_budget_tokens",
|
|
319
|
+
str(getattr(ctx, "dynamic_context_budget_tokens", "n/a")),
|
|
320
|
+
)
|
|
321
|
+
dynamic_table.add_row(
|
|
322
|
+
"dynamic_context_used_tokens",
|
|
323
|
+
str(getattr(ctx, "dynamic_context_used_tokens", "n/a")),
|
|
324
|
+
)
|
|
325
|
+
dynamic_table.add_row(
|
|
326
|
+
"dynamic_context_left_tokens",
|
|
327
|
+
str(getattr(ctx, "dynamic_context_remaining_tokens", "n/a")),
|
|
328
|
+
)
|
|
329
|
+
dynamic_table.add_row(
|
|
330
|
+
"dynamic_context_left_percent",
|
|
331
|
+
f"{dynamic_percent_left:.1f}%" if dynamic_percent_left is not None else "n/a",
|
|
332
|
+
)
|
|
333
|
+
console.print(dynamic_table)
|
|
334
|
+
|
|
335
|
+
if effective_input_budget is not None and total_request_tokens > effective_input_budget:
|
|
336
|
+
console.print(
|
|
337
|
+
"[yellow]Request estimate exceeds effective input budget.[/yellow] "
|
|
338
|
+
"Run /compact to reduce context."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
sanitized_messages = _sanitize_messages_for_estimation(messages)
|
|
342
|
+
pinned_messages: list[dict[str, Any]] = []
|
|
343
|
+
memory_messages: list[dict[str, Any]] = []
|
|
344
|
+
pins_messages: list[dict[str, Any]] = []
|
|
345
|
+
conversation_messages: list[dict[str, Any]] = []
|
|
346
|
+
for idx, msg in enumerate(sanitized_messages):
|
|
347
|
+
if idx < pinned_prefix_len:
|
|
348
|
+
pinned_messages.append(msg)
|
|
349
|
+
continue
|
|
350
|
+
content = msg.get("content")
|
|
351
|
+
if isinstance(content, str) and content.startswith(_memory_marker()):
|
|
352
|
+
memory_messages.append(msg)
|
|
353
|
+
continue
|
|
354
|
+
if isinstance(content, str) and content.startswith(_pins_marker()):
|
|
355
|
+
pins_messages.append(msg)
|
|
356
|
+
continue
|
|
357
|
+
conversation_messages.append(msg)
|
|
358
|
+
|
|
359
|
+
def _bucket_tokens(bucket: list[dict[str, Any]]) -> int:
|
|
360
|
+
if not bucket:
|
|
361
|
+
return 0
|
|
362
|
+
return estimate_tokens(json.dumps(bucket, ensure_ascii=False, sort_keys=True))
|
|
363
|
+
|
|
364
|
+
pinned_tokens = _bucket_tokens(pinned_messages)
|
|
365
|
+
memory_tokens = _bucket_tokens(memory_messages)
|
|
366
|
+
pins_tokens = _bucket_tokens(pins_messages)
|
|
367
|
+
conversation_tokens = _bucket_tokens(conversation_messages)
|
|
368
|
+
|
|
369
|
+
breakdown_table = _Table(title="Context Breakdown")
|
|
370
|
+
breakdown_table.add_column("field")
|
|
371
|
+
breakdown_table.add_column("value")
|
|
372
|
+
breakdown_table.add_row("pinned_prefix_tokens", str(pinned_tokens))
|
|
373
|
+
breakdown_table.add_row("memory_tokens", str(memory_tokens))
|
|
374
|
+
breakdown_table.add_row("pins_tokens", str(pins_tokens))
|
|
375
|
+
breakdown_table.add_row("conversation_tokens", str(conversation_tokens))
|
|
376
|
+
breakdown_table.add_row("total_messages_count", str(len(messages)))
|
|
377
|
+
console.print(breakdown_table)
|
|
378
|
+
|
|
379
|
+
root = Path(getattr(session, "root", Path(".")))
|
|
380
|
+
store_obj = getattr(session, "store", None)
|
|
381
|
+
session_id = str(getattr(store_obj, "session_id", "") or "")
|
|
382
|
+
history_session_dir = None
|
|
383
|
+
if compactor is not None and hasattr(compactor, "history_dir"):
|
|
384
|
+
history_dir_obj = getattr(compactor, "history_dir", None)
|
|
385
|
+
if isinstance(history_dir_obj, Path):
|
|
386
|
+
history_session_dir = history_dir_obj.parent
|
|
387
|
+
if history_session_dir is None and session_id:
|
|
388
|
+
history_session_dir = root / ".alysis" / "sessions" / _safe_component(session_id)
|
|
389
|
+
tool_output_session_dir = getattr(store_obj, "session_artifact_root", None)
|
|
390
|
+
history_chunks_count = 0
|
|
391
|
+
offloaded_tool_outputs_count = 0
|
|
392
|
+
summary_exists = False
|
|
393
|
+
pins_file_exists = False
|
|
394
|
+
if history_session_dir is not None:
|
|
395
|
+
history_chunks_count = len(list((history_session_dir / "history").glob("chunk_*.jsonl")))
|
|
396
|
+
summary_exists = (history_session_dir / "memory" / "summary.json").exists()
|
|
397
|
+
pins_file_exists = (history_session_dir / "memory" / "pins.json").exists()
|
|
398
|
+
if (
|
|
399
|
+
history_session_dir is not None
|
|
400
|
+
and session_id
|
|
401
|
+
and history_chunks_count == 0
|
|
402
|
+
and not summary_exists
|
|
403
|
+
and not pins_file_exists
|
|
404
|
+
):
|
|
405
|
+
legacy_history_session_dir = root / ".alysis" / "sessions" / _safe_component(session_id)
|
|
406
|
+
if legacy_history_session_dir != history_session_dir:
|
|
407
|
+
history_chunks_count = len(
|
|
408
|
+
list((legacy_history_session_dir / "history").glob("chunk_*.jsonl"))
|
|
409
|
+
)
|
|
410
|
+
summary_exists = (legacy_history_session_dir / "memory" / "summary.json").exists()
|
|
411
|
+
pins_file_exists = (legacy_history_session_dir / "memory" / "pins.json").exists()
|
|
412
|
+
if isinstance(tool_output_session_dir, Path):
|
|
413
|
+
offloaded_tool_outputs_count = len(
|
|
414
|
+
list((tool_output_session_dir / "tool_outputs").glob("*.json"))
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
pins_count = 0
|
|
418
|
+
if compactor is not None and hasattr(compactor, "state"):
|
|
419
|
+
history_chunks_count = max(
|
|
420
|
+
history_chunks_count,
|
|
421
|
+
int(getattr(compactor.state, "history_chunk_index", 0) or 0),
|
|
422
|
+
)
|
|
423
|
+
pins = getattr(compactor.state, "pins", [])
|
|
424
|
+
if isinstance(pins, list):
|
|
425
|
+
pins_count = len(pins)
|
|
426
|
+
|
|
427
|
+
compaction_table = _Table(title="Compaction Artifacts")
|
|
428
|
+
compaction_table.add_column("field")
|
|
429
|
+
compaction_table.add_column("value")
|
|
430
|
+
compaction_table.add_row("compaction_enabled", "yes" if compactor is not None else "no")
|
|
431
|
+
compaction_table.add_row(
|
|
432
|
+
"compaction_profile",
|
|
433
|
+
str(getattr(compactor, "profile_name", "-")) if compactor is not None else "-",
|
|
434
|
+
)
|
|
435
|
+
compaction_table.add_row(
|
|
436
|
+
"compactor_model",
|
|
437
|
+
str(getattr(getattr(compactor, "compactor_client", None), "model", "-")),
|
|
438
|
+
)
|
|
439
|
+
compaction_table.add_row("history_chunks_count", str(history_chunks_count))
|
|
440
|
+
compaction_table.add_row("pins_count", str(pins_count))
|
|
441
|
+
compaction_table.add_row("offloaded_tool_outputs_count", str(offloaded_tool_outputs_count))
|
|
442
|
+
compaction_table.add_row("summary_file_exists", "yes" if summary_exists else "no")
|
|
443
|
+
compaction_table.add_row("pins_file_exists", "yes" if pins_file_exists else "no")
|
|
444
|
+
console.print(compaction_table)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ...config import AppConfig
|
|
7
|
+
from ...forge import RunPaths
|
|
8
|
+
from ...plan_repair import ClarificationLoopTracker
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class _ForgePlannerSessionState:
|
|
13
|
+
transcript: list[dict[str, str]] = field(default_factory=list)
|
|
14
|
+
cfg: AppConfig | None = None
|
|
15
|
+
workspace_context: dict[str, Any] | None = None
|
|
16
|
+
awaiting_clarification: bool = False
|
|
17
|
+
pending_questions: list[str] = field(default_factory=list)
|
|
18
|
+
# Consecutive clarification-only planner turns for the current goal. Past the
|
|
19
|
+
# policy cap the planner is made to draft a plan instead of asking again.
|
|
20
|
+
clarification_loop: ClarificationLoopTracker = field(default_factory=ClarificationLoopTracker)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class _ForgeEnterCommand:
|
|
25
|
+
entry_mode: str
|
|
26
|
+
initial_goal: str = ""
|
|
27
|
+
usage_error: str | None = None
|
|
28
|
+
# Explicit plan-assistant choice carried by the ``/forge --planner`` /
|
|
29
|
+
# ``/forge --no-planner`` forms (emitted by the TUI intro popup's native
|
|
30
|
+
# "Use the planner?" prompt, and usable directly). ``None`` means "no
|
|
31
|
+
# explicit choice" — the caller falls back to its own default.
|
|
32
|
+
planner_assistant_default: bool | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class _ForgeChatEntrySelection:
|
|
37
|
+
paths: RunPaths
|
|
38
|
+
plan: dict[str, Any]
|
|
39
|
+
entry_kind: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class _ForgeChatState:
|
|
44
|
+
ui_mode: str = "chat"
|
|
45
|
+
paths: RunPaths | None = None
|
|
46
|
+
plan: dict[str, Any] | None = None
|
|
47
|
+
assistant_enabled: bool = False
|
|
48
|
+
entry_request_mode: str = "plain"
|
|
49
|
+
entry_goal: str = ""
|
|
50
|
+
planner_session: _ForgePlannerSessionState = field(default_factory=_ForgePlannerSessionState)
|
|
51
|
+
# Swarm knobs chosen in the TUI launch gate (``/execute plan``), read by the
|
|
52
|
+
# ``/execute`` command handler when present. Empty dict → the handler's own
|
|
53
|
+
# defaults apply (so the classic, non-TUI path is unchanged). Recognised keys:
|
|
54
|
+
# ``parallel`` (int), ``scope_mode`` ("strict"|"warn"), ``verify_mode``
|
|
55
|
+
# ("warn"|"strict"|"off"), ``review`` (bool).
|
|
56
|
+
swarm_knobs: dict[str, Any] = field(default_factory=dict)
|
|
57
|
+
# True once the current ``/execute plan`` invocation actually reached
|
|
58
|
+
# ``run_swarm`` (reset at the top of each invocation). Lets the TUI worker
|
|
59
|
+
# distinguish "the handler rejected the execute before anything ran" from
|
|
60
|
+
# "a swarm ran" when finalizing the live forge view.
|
|
61
|
+
swarm_run_attempted: bool = False
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def planner_transcript(self) -> list[dict[str, str]]:
|
|
65
|
+
return self.planner_session.transcript
|
|
66
|
+
|
|
67
|
+
@planner_transcript.setter
|
|
68
|
+
def planner_transcript(self, value: list[dict[str, str]]) -> None:
|
|
69
|
+
self.planner_session.transcript = list(value)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def planner_cfg(self) -> AppConfig | None:
|
|
73
|
+
return self.planner_session.cfg
|
|
74
|
+
|
|
75
|
+
@planner_cfg.setter
|
|
76
|
+
def planner_cfg(self, value: AppConfig | None) -> None:
|
|
77
|
+
self.planner_session.cfg = value
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def workspace_context(self) -> dict[str, Any] | None:
|
|
81
|
+
return self.planner_session.workspace_context
|
|
82
|
+
|
|
83
|
+
@workspace_context.setter
|
|
84
|
+
def workspace_context(self, value: dict[str, Any] | None) -> None:
|
|
85
|
+
self.planner_session.workspace_context = value
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def planner_awaiting_clarification(self) -> bool:
|
|
89
|
+
return bool(self.planner_session.awaiting_clarification)
|
|
90
|
+
|
|
91
|
+
@planner_awaiting_clarification.setter
|
|
92
|
+
def planner_awaiting_clarification(self, value: bool) -> None:
|
|
93
|
+
self.planner_session.awaiting_clarification = bool(value)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def planner_pending_questions(self) -> list[str]:
|
|
97
|
+
return self.planner_session.pending_questions
|
|
98
|
+
|
|
99
|
+
@planner_pending_questions.setter
|
|
100
|
+
def planner_pending_questions(self, value: list[str]) -> None:
|
|
101
|
+
self.planner_session.pending_questions = list(value)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class _ChatPlanModeState:
|
|
106
|
+
enabled: bool = False
|
|
107
|
+
restore_mode: str | None = None
|
|
108
|
+
latest_task: str | None = None
|
|
109
|
+
latest_draft: str | None = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class _ChatExecutionRequest:
|
|
114
|
+
instruction: str
|
|
115
|
+
routing_mode_override: str | None = None
|
|
116
|
+
ephemeral_system_messages: tuple[str, ...] = ()
|
|
117
|
+
ephemeral_user_messages: tuple[str, ...] = ()
|
|
118
|
+
mode_override: str | None = None
|
|
119
|
+
restore_mode_after: str | None = None
|
|
120
|
+
plan_mode_capture_task: str | None = None
|
|
121
|
+
chat_only: bool = False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
__all__ = [name for name in globals() if not name.startswith("__") or name == "__version__"]
|