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,466 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from dataclasses import dataclass, replace
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ..config import AppConfig
|
|
10
|
+
from ..model_registry import ModelRegistry
|
|
11
|
+
from .models import AssetError, AssetRecord, ComprehensionRecord
|
|
12
|
+
from .plan_binding import task_asset_briefing
|
|
13
|
+
from .surface import AssetSurface, SurfaceComprehensionStatus
|
|
14
|
+
|
|
15
|
+
LOGGER = logging.getLogger(__name__)
|
|
16
|
+
_TRUNCATION_SUFFIX = "... (truncated for context budget)"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class PlannerAssetsContext:
|
|
21
|
+
text_block: str
|
|
22
|
+
inline_image_paths: list[str]
|
|
23
|
+
pending_asset_ids: list[str]
|
|
24
|
+
failed_asset_ids: list[str]
|
|
25
|
+
minimal_asset_ids: list[str]
|
|
26
|
+
referenced_asset_ids: list[str]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AssetReadinessPolicy(Enum):
|
|
30
|
+
BLOCK = "block"
|
|
31
|
+
SOFT = "soft"
|
|
32
|
+
PARTIAL = "partial"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class AssetReadinessReport:
|
|
37
|
+
ready: list[str]
|
|
38
|
+
pending: list[str]
|
|
39
|
+
failed: list[str]
|
|
40
|
+
minimal: list[str]
|
|
41
|
+
timed_out: list[str]
|
|
42
|
+
policy: AssetReadinessPolicy
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class AssetReferenceReport:
|
|
47
|
+
deleted_referenced: list[tuple[str, str]]
|
|
48
|
+
missing_referenced: list[tuple[str, str]]
|
|
49
|
+
pinned_added: list[str]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class PlannerAssetsBundle:
|
|
54
|
+
context: PlannerAssetsContext
|
|
55
|
+
inline_image_paths: list[str]
|
|
56
|
+
readiness_report: AssetReadinessReport
|
|
57
|
+
reference_report: AssetReferenceReport
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_planner_assets_block(
|
|
61
|
+
surface: AssetSurface,
|
|
62
|
+
*,
|
|
63
|
+
include_pending: bool = False,
|
|
64
|
+
) -> PlannerAssetsContext:
|
|
65
|
+
entries = sorted(
|
|
66
|
+
surface.list_assets(include_deleted=False),
|
|
67
|
+
key=lambda entry: (not entry.record.pinned, entry.record.id),
|
|
68
|
+
)
|
|
69
|
+
max_chars = max(int(surface.cfg.assets.planner.max_chars_per_asset), len(_TRUNCATION_SUFFIX))
|
|
70
|
+
lines = [
|
|
71
|
+
"## Available Assets",
|
|
72
|
+
"",
|
|
73
|
+
"The user has provided the following resources for this run. Use them when they help. "
|
|
74
|
+
"You may inspect text assets via `asset_read` and request re-comprehension via "
|
|
75
|
+
"`asset_inspect`. Do not invent asset ids.",
|
|
76
|
+
]
|
|
77
|
+
inline_image_paths: list[str] = []
|
|
78
|
+
pending: list[str] = []
|
|
79
|
+
failed: list[str] = []
|
|
80
|
+
minimal: list[str] = []
|
|
81
|
+
|
|
82
|
+
if not entries:
|
|
83
|
+
lines.extend(["", "- (no assets attached)"])
|
|
84
|
+
|
|
85
|
+
for entry in entries:
|
|
86
|
+
record = entry.record
|
|
87
|
+
status = entry.comprehension_status
|
|
88
|
+
if status in {"pending", "running"}:
|
|
89
|
+
pending.append(record.id)
|
|
90
|
+
if not include_pending:
|
|
91
|
+
continue
|
|
92
|
+
elif status == "failed":
|
|
93
|
+
failed.append(record.id)
|
|
94
|
+
elif status == "minimal":
|
|
95
|
+
minimal.append(record.id)
|
|
96
|
+
comprehension = surface.comprehension_for(record.id)
|
|
97
|
+
block = _asset_block(
|
|
98
|
+
record=record,
|
|
99
|
+
status=status,
|
|
100
|
+
comprehension=comprehension,
|
|
101
|
+
max_chars=max_chars,
|
|
102
|
+
)
|
|
103
|
+
lines.extend(["", block])
|
|
104
|
+
if (
|
|
105
|
+
record.kind == "image"
|
|
106
|
+
and status in {"ready", "minimal", "failed"}
|
|
107
|
+
and record.stored_path
|
|
108
|
+
):
|
|
109
|
+
inline_image_paths.append(str((surface.run_paths.root / record.stored_path).resolve()))
|
|
110
|
+
|
|
111
|
+
context = PlannerAssetsContext(
|
|
112
|
+
text_block="\n".join(lines).rstrip(),
|
|
113
|
+
inline_image_paths=inline_image_paths,
|
|
114
|
+
pending_asset_ids=pending,
|
|
115
|
+
failed_asset_ids=failed,
|
|
116
|
+
minimal_asset_ids=minimal,
|
|
117
|
+
referenced_asset_ids=[],
|
|
118
|
+
)
|
|
119
|
+
LOGGER.info(
|
|
120
|
+
"planner_assets_block assets_total=%s ready=%s minimal=%s failed=%s pending=%s image_inline=%s",
|
|
121
|
+
len(entries),
|
|
122
|
+
len([entry for entry in entries if entry.comprehension_status == "ready"]),
|
|
123
|
+
len(minimal),
|
|
124
|
+
len(failed),
|
|
125
|
+
len(pending),
|
|
126
|
+
len(inline_image_paths),
|
|
127
|
+
)
|
|
128
|
+
return context
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def ensure_planner_asset_readiness(
|
|
132
|
+
surface: AssetSurface,
|
|
133
|
+
*,
|
|
134
|
+
policy: AssetReadinessPolicy,
|
|
135
|
+
timeout_seconds: float | None = None,
|
|
136
|
+
) -> AssetReadinessReport:
|
|
137
|
+
timed_out: list[str] = []
|
|
138
|
+
_start_pending_comprehensions(surface)
|
|
139
|
+
if policy is AssetReadinessPolicy.BLOCK:
|
|
140
|
+
join_report = surface.join_pending(timeout_seconds=timeout_seconds)
|
|
141
|
+
timed_out = join_report.timed_out
|
|
142
|
+
|
|
143
|
+
report = _readiness_snapshot(surface=surface, policy=policy, timed_out=timed_out)
|
|
144
|
+
LOGGER.info(
|
|
145
|
+
"planner_assets_readiness policy=%s ready=%s pending=%s timed_out=%s",
|
|
146
|
+
policy.value,
|
|
147
|
+
len(report.ready),
|
|
148
|
+
len(report.pending),
|
|
149
|
+
len(report.timed_out),
|
|
150
|
+
)
|
|
151
|
+
if policy is AssetReadinessPolicy.BLOCK and (report.pending or report.timed_out):
|
|
152
|
+
offenders = list(dict.fromkeys([*report.pending, *report.timed_out]))
|
|
153
|
+
raise AssetError(
|
|
154
|
+
"Asset comprehension did not finish before planner readiness timeout: "
|
|
155
|
+
+ ", ".join(offenders)
|
|
156
|
+
)
|
|
157
|
+
return report
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def asset_reference_check(
|
|
161
|
+
plan: dict[str, Any] | None, surface: AssetSurface
|
|
162
|
+
) -> AssetReferenceReport:
|
|
163
|
+
if not isinstance(plan, dict):
|
|
164
|
+
return AssetReferenceReport(deleted_referenced=[], missing_referenced=[], pinned_added=[])
|
|
165
|
+
records = {record.id: record for record in surface.index.records(include_deleted=True)}
|
|
166
|
+
referenced_ids: set[str] = set()
|
|
167
|
+
deleted: list[tuple[str, str]] = []
|
|
168
|
+
missing: list[tuple[str, str]] = []
|
|
169
|
+
for task in plan.get("tasks") or []:
|
|
170
|
+
if not isinstance(task, dict):
|
|
171
|
+
continue
|
|
172
|
+
task_id = str(task.get("id") or "").strip() or "(unknown)"
|
|
173
|
+
try:
|
|
174
|
+
briefing = task_asset_briefing(task)
|
|
175
|
+
except AssetError:
|
|
176
|
+
continue
|
|
177
|
+
if briefing is None:
|
|
178
|
+
continue
|
|
179
|
+
for entry in [*briefing.primary, *briefing.may_need]:
|
|
180
|
+
referenced_ids.add(entry.asset_id)
|
|
181
|
+
record = records.get(entry.asset_id)
|
|
182
|
+
if record is None:
|
|
183
|
+
missing.append((task_id, entry.asset_id))
|
|
184
|
+
elif record.deleted_at is not None:
|
|
185
|
+
deleted.append((task_id, entry.asset_id))
|
|
186
|
+
pinned_added = sorted(
|
|
187
|
+
record.id
|
|
188
|
+
for record in records.values()
|
|
189
|
+
if record.pinned and record.deleted_at is None and record.id not in referenced_ids
|
|
190
|
+
)
|
|
191
|
+
report = AssetReferenceReport(
|
|
192
|
+
deleted_referenced=list(dict.fromkeys(deleted)),
|
|
193
|
+
missing_referenced=list(dict.fromkeys(missing)),
|
|
194
|
+
pinned_added=pinned_added,
|
|
195
|
+
)
|
|
196
|
+
LOGGER.info(
|
|
197
|
+
"planner_assets_reference_check deleted_referenced=%s missing_referenced=%s pinned_added=%s",
|
|
198
|
+
len(report.deleted_referenced),
|
|
199
|
+
len(report.missing_referenced),
|
|
200
|
+
len(report.pinned_added),
|
|
201
|
+
)
|
|
202
|
+
return report
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def build_planner_assets_bundle(
|
|
206
|
+
*,
|
|
207
|
+
cfg: AppConfig,
|
|
208
|
+
surface: AssetSurface,
|
|
209
|
+
plan: dict[str, Any] | None,
|
|
210
|
+
role_model: str,
|
|
211
|
+
model_registry: ModelRegistry,
|
|
212
|
+
) -> PlannerAssetsBundle:
|
|
213
|
+
policy = AssetReadinessPolicy(str(cfg.assets.planner.readiness_policy))
|
|
214
|
+
readiness = ensure_planner_asset_readiness(
|
|
215
|
+
surface,
|
|
216
|
+
policy=policy,
|
|
217
|
+
timeout_seconds=float(cfg.assets.planner.readiness_timeout_seconds),
|
|
218
|
+
)
|
|
219
|
+
context = build_planner_assets_block(
|
|
220
|
+
surface,
|
|
221
|
+
include_pending=policy is AssetReadinessPolicy.PARTIAL,
|
|
222
|
+
)
|
|
223
|
+
reference_report = asset_reference_check(plan, surface)
|
|
224
|
+
referenced_ids = sorted(_collect_referenced_asset_ids_best_effort(plan or {}))
|
|
225
|
+
if reference_report.deleted_referenced or reference_report.missing_referenced:
|
|
226
|
+
context = replace(
|
|
227
|
+
context,
|
|
228
|
+
text_block=context.text_block + _drift_block(reference_report),
|
|
229
|
+
referenced_asset_ids=referenced_ids,
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
context = replace(context, referenced_asset_ids=referenced_ids)
|
|
233
|
+
inline_paths: list[str] = []
|
|
234
|
+
if cfg.assets.planner.inline_images and _model_supports_vision(model_registry, role_model):
|
|
235
|
+
inline_paths = context.inline_image_paths[
|
|
236
|
+
: max(int(cfg.assets.planner.max_inline_images), 0)
|
|
237
|
+
]
|
|
238
|
+
return PlannerAssetsBundle(
|
|
239
|
+
context=context,
|
|
240
|
+
inline_image_paths=inline_paths,
|
|
241
|
+
readiness_report=readiness,
|
|
242
|
+
reference_report=reference_report,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _asset_block(
|
|
247
|
+
*,
|
|
248
|
+
record: AssetRecord,
|
|
249
|
+
status: SurfaceComprehensionStatus,
|
|
250
|
+
comprehension: ComprehensionRecord | None,
|
|
251
|
+
max_chars: int,
|
|
252
|
+
) -> str:
|
|
253
|
+
header = f'### {record.id} - "{record.title}"'
|
|
254
|
+
if status in {"pending", "running"}:
|
|
255
|
+
return _truncate_block(
|
|
256
|
+
"\n".join(
|
|
257
|
+
[
|
|
258
|
+
header,
|
|
259
|
+
"- Status: comprehension pending - content not yet structured.",
|
|
260
|
+
f"- Kind: {_kind_label(record)}",
|
|
261
|
+
"- The planner should not rely on this asset until comprehension is ready.",
|
|
262
|
+
]
|
|
263
|
+
),
|
|
264
|
+
max_chars=max_chars,
|
|
265
|
+
)
|
|
266
|
+
if status == "minimal":
|
|
267
|
+
lines = [
|
|
268
|
+
header,
|
|
269
|
+
"- Status: minimal comprehension - only user-provided metadata is available.",
|
|
270
|
+
f"- Description: {record.description or '(none)'}",
|
|
271
|
+
f"- Kind: {_kind_label(record)}",
|
|
272
|
+
"- Treat as low-confidence context.",
|
|
273
|
+
]
|
|
274
|
+
if record.kind == "image":
|
|
275
|
+
lines.append(
|
|
276
|
+
"- Visual/OCR note: no structured visual or OCR understanding is available; "
|
|
277
|
+
"ask the user or refresh after configuring a vision/OCR provider if the image matters."
|
|
278
|
+
)
|
|
279
|
+
return _truncate_block("\n".join(lines), max_chars=max_chars)
|
|
280
|
+
if status == "failed":
|
|
281
|
+
return _truncate_block(
|
|
282
|
+
"\n".join(
|
|
283
|
+
[
|
|
284
|
+
header,
|
|
285
|
+
"- Status: comprehension failed - ask the user about this asset if it matters.",
|
|
286
|
+
f"- Kind: {_kind_label(record)}",
|
|
287
|
+
f"- Error: {(comprehension.error if comprehension else '') or 'unknown'}",
|
|
288
|
+
]
|
|
289
|
+
),
|
|
290
|
+
max_chars=max_chars,
|
|
291
|
+
)
|
|
292
|
+
if comprehension is None:
|
|
293
|
+
return _truncate_block(
|
|
294
|
+
"\n".join(
|
|
295
|
+
[
|
|
296
|
+
header,
|
|
297
|
+
"- Status: comprehension unavailable.",
|
|
298
|
+
f"- Kind: {_kind_label(record)}",
|
|
299
|
+
]
|
|
300
|
+
),
|
|
301
|
+
max_chars=max_chars,
|
|
302
|
+
)
|
|
303
|
+
lines = [
|
|
304
|
+
header,
|
|
305
|
+
f"- Kind: {_kind_label(record)}",
|
|
306
|
+
"- Source: "
|
|
307
|
+
+ _source_line(comprehension)
|
|
308
|
+
+ f" - confidence: {_confidence_label(comprehension)}",
|
|
309
|
+
f"- Pinned: {'yes' if record.pinned else 'no'}",
|
|
310
|
+
f"- Summary: {comprehension.data.semantic_summary or '(none)'}",
|
|
311
|
+
]
|
|
312
|
+
_append_entities(lines, comprehension)
|
|
313
|
+
_append_list(lines, "Stated facts", comprehension.data.stated_facts)
|
|
314
|
+
_append_list(lines, "Stated decisions", comprehension.data.stated_decisions)
|
|
315
|
+
_append_list(lines, "Stated constraints", comprehension.data.stated_constraints)
|
|
316
|
+
_append_list(lines, "Actionable signals", comprehension.data.actionable_signals)
|
|
317
|
+
_append_list(lines, "Open questions", comprehension.data.open_questions)
|
|
318
|
+
return _truncate_block("\n".join(lines), max_chars=max_chars)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _source_line(comprehension: ComprehensionRecord) -> str:
|
|
322
|
+
lang = comprehension.detected_language or "unknown"
|
|
323
|
+
if comprehension.language_confidence is None:
|
|
324
|
+
return f"{comprehension.source} - detected language: {lang}"
|
|
325
|
+
return f"{comprehension.source} - detected language: {lang} ({comprehension.language_confidence:.2f})"
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _append_entities(lines: list[str], comprehension: ComprehensionRecord) -> None:
|
|
329
|
+
entities = []
|
|
330
|
+
for item in comprehension.data.key_entities[:12]:
|
|
331
|
+
if not isinstance(item, dict):
|
|
332
|
+
continue
|
|
333
|
+
entity_type = str(item.get("type") or "").strip()
|
|
334
|
+
value = str(item.get("value") or "").strip()
|
|
335
|
+
if entity_type and value:
|
|
336
|
+
entities.append(f"{entity_type}={value}")
|
|
337
|
+
if entities:
|
|
338
|
+
lines.append("- Key entities: " + ", ".join(entities))
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _append_list(lines: list[str], label: str, values: list[str]) -> None:
|
|
342
|
+
clean_values = [str(value).strip() for value in values if str(value).strip()]
|
|
343
|
+
if not clean_values:
|
|
344
|
+
return
|
|
345
|
+
lines.append(f"- {label}:")
|
|
346
|
+
for value in clean_values[:8]:
|
|
347
|
+
lines.append(f" - {value}")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _kind_label(record: AssetRecord) -> str:
|
|
351
|
+
subtype = record.mime.split("/")[-1] if record.mime else "unknown"
|
|
352
|
+
return f"{record.kind} ({subtype}, {_format_size(record.size_bytes)})"
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _format_size(size_bytes: int) -> str:
|
|
356
|
+
size = float(max(size_bytes, 0))
|
|
357
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
358
|
+
if size < 1024 or unit == "GB":
|
|
359
|
+
return f"{int(size)} {unit}" if unit == "B" else f"{size:.0f} {unit}"
|
|
360
|
+
size /= 1024
|
|
361
|
+
return f"{size_bytes} B"
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _confidence_label(comprehension: ComprehensionRecord) -> str:
|
|
365
|
+
score = float(comprehension.confidence_modifier or 0.0)
|
|
366
|
+
if score >= 0.8:
|
|
367
|
+
return "high"
|
|
368
|
+
if score >= 0.55:
|
|
369
|
+
return "medium"
|
|
370
|
+
return "low"
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _truncate_block(text: str, *, max_chars: int) -> str:
|
|
374
|
+
if len(text) <= max_chars:
|
|
375
|
+
return text
|
|
376
|
+
limit = max(max_chars - len(_TRUNCATION_SUFFIX), 0)
|
|
377
|
+
return text[:limit].rstrip() + _TRUNCATION_SUFFIX
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _readiness_snapshot(
|
|
381
|
+
*,
|
|
382
|
+
surface: AssetSurface,
|
|
383
|
+
policy: AssetReadinessPolicy,
|
|
384
|
+
timed_out: list[str],
|
|
385
|
+
) -> AssetReadinessReport:
|
|
386
|
+
ready: list[str] = []
|
|
387
|
+
pending: list[str] = []
|
|
388
|
+
failed: list[str] = []
|
|
389
|
+
minimal: list[str] = []
|
|
390
|
+
for entry in surface.list_assets(include_deleted=False):
|
|
391
|
+
status = entry.comprehension_status
|
|
392
|
+
if status == "ready":
|
|
393
|
+
ready.append(entry.record.id)
|
|
394
|
+
elif status in {"pending", "running"}:
|
|
395
|
+
pending.append(entry.record.id)
|
|
396
|
+
elif status == "failed":
|
|
397
|
+
failed.append(entry.record.id)
|
|
398
|
+
elif status == "minimal":
|
|
399
|
+
minimal.append(entry.record.id)
|
|
400
|
+
return AssetReadinessReport(
|
|
401
|
+
ready=ready,
|
|
402
|
+
pending=pending,
|
|
403
|
+
failed=failed,
|
|
404
|
+
minimal=minimal,
|
|
405
|
+
timed_out=timed_out,
|
|
406
|
+
policy=policy,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _start_pending_comprehensions(surface: AssetSurface) -> list[str]:
|
|
411
|
+
started: list[str] = []
|
|
412
|
+
for entry in surface.list_assets(include_deleted=False):
|
|
413
|
+
if entry.comprehension_status != "pending":
|
|
414
|
+
continue
|
|
415
|
+
try:
|
|
416
|
+
surface.refresh_comprehension(entry.record.id, mode="async")
|
|
417
|
+
except AssetError as exc:
|
|
418
|
+
LOGGER.warning(
|
|
419
|
+
"planner_assets_readiness failed to start comprehension asset_id=%s: %s",
|
|
420
|
+
entry.record.id,
|
|
421
|
+
exc,
|
|
422
|
+
)
|
|
423
|
+
continue
|
|
424
|
+
started.append(entry.record.id)
|
|
425
|
+
return started
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _collect_referenced_asset_ids_best_effort(plan: dict[str, Any]) -> set[str]:
|
|
429
|
+
referenced: set[str] = set()
|
|
430
|
+
for task in plan.get("tasks") or []:
|
|
431
|
+
if not isinstance(task, dict):
|
|
432
|
+
continue
|
|
433
|
+
try:
|
|
434
|
+
briefing = task_asset_briefing(task)
|
|
435
|
+
except AssetError:
|
|
436
|
+
continue
|
|
437
|
+
if briefing is None:
|
|
438
|
+
continue
|
|
439
|
+
referenced.update(entry.asset_id for entry in briefing.primary)
|
|
440
|
+
referenced.update(entry.asset_id for entry in briefing.may_need)
|
|
441
|
+
return referenced
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _drift_block(report: AssetReferenceReport) -> str:
|
|
445
|
+
lines = ["", "", "## Plan-Asset Drift", ""]
|
|
446
|
+
if report.deleted_referenced:
|
|
447
|
+
lines.append("Deleted assets are still referenced by tasks:")
|
|
448
|
+
for task_id, asset_id in report.deleted_referenced:
|
|
449
|
+
lines.append(f"- {task_id}: {asset_id}")
|
|
450
|
+
if report.missing_referenced:
|
|
451
|
+
lines.append("Missing assets are still referenced by tasks:")
|
|
452
|
+
for task_id, asset_id in report.missing_referenced:
|
|
453
|
+
lines.append(f"- {task_id}: {asset_id}")
|
|
454
|
+
lines.append("Revise affected task asset_briefing entries before relying on them.")
|
|
455
|
+
return "\n".join(lines)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _model_supports_vision(model_registry: ModelRegistry, role_model: str) -> bool:
|
|
459
|
+
try:
|
|
460
|
+
return bool(model_registry.get(role_model).supports_vision)
|
|
461
|
+
except Exception: # noqa: BLE001 - metadata policy handles hard failures elsewhere
|
|
462
|
+
return False
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def absolute_asset_path(root: Path, stored_path: str) -> str:
|
|
466
|
+
return str((root / stored_path).resolve())
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from ..config import AppConfig
|
|
9
|
+
from ..forge import RunPaths
|
|
10
|
+
from ..llm.factory import make_llm_client
|
|
11
|
+
from ..model_registry import ModelRegistry
|
|
12
|
+
from .asset_read_core import perform_asset_read
|
|
13
|
+
from .models import AssetError, ComprehensionRecord
|
|
14
|
+
from .surface import AssetSurface
|
|
15
|
+
|
|
16
|
+
LOGGER = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
ASSET_READ_TOOL: dict[str, Any] = {
|
|
19
|
+
"type": "function",
|
|
20
|
+
"function": {
|
|
21
|
+
"name": "asset_read",
|
|
22
|
+
"description": (
|
|
23
|
+
"Read the extracted text of an asset, optionally focused on a topic the planner cares about."
|
|
24
|
+
),
|
|
25
|
+
"parameters": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"additionalProperties": False,
|
|
28
|
+
"required": ["asset_id"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"asset_id": {"type": "string"},
|
|
31
|
+
"focus": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": (
|
|
34
|
+
"Optional topic to focus the extraction on. When provided, runs a small LLM "
|
|
35
|
+
"extraction pass to return only the relevant content."
|
|
36
|
+
),
|
|
37
|
+
},
|
|
38
|
+
"max_chars": {
|
|
39
|
+
"type": "integer",
|
|
40
|
+
"description": "Cap on returned text. Default 8000.",
|
|
41
|
+
"minimum": 100,
|
|
42
|
+
"maximum": 32000,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ASSET_INSPECT_TOOL: dict[str, Any] = {
|
|
50
|
+
"type": "function",
|
|
51
|
+
"function": {
|
|
52
|
+
"name": "asset_inspect",
|
|
53
|
+
"description": "Request a re-comprehension of an asset using a specific angle or framing.",
|
|
54
|
+
"parameters": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": False,
|
|
57
|
+
"required": ["asset_id"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"asset_id": {"type": "string"},
|
|
60
|
+
"angle": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": (
|
|
63
|
+
"Optional framing for the re-comprehension, for example security perspective "
|
|
64
|
+
"or data-flow focus."
|
|
65
|
+
),
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
PLANNER_ASSET_TOOLS: list[dict[str, Any]] = [ASSET_READ_TOOL, ASSET_INSPECT_TOOL]
|
|
73
|
+
_INSPECT_CAP_PER_ASSET = 3
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PlannerAssetToolRunner:
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
*,
|
|
80
|
+
cfg: AppConfig,
|
|
81
|
+
run_paths: RunPaths,
|
|
82
|
+
surface: AssetSurface,
|
|
83
|
+
model_registry: ModelRegistry,
|
|
84
|
+
api_key: str | None = None,
|
|
85
|
+
transport: httpx.BaseTransport | None = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
self.cfg = cfg
|
|
88
|
+
self.run_paths = run_paths
|
|
89
|
+
self.surface = surface
|
|
90
|
+
self.model_registry = model_registry
|
|
91
|
+
self.api_key = api_key
|
|
92
|
+
self.transport = transport
|
|
93
|
+
self._inspect_counts: dict[str, int] = {}
|
|
94
|
+
|
|
95
|
+
def dispatch(self, name: str, arguments: dict[str, Any]) -> str:
|
|
96
|
+
if name == "asset_read":
|
|
97
|
+
return self.asset_read(
|
|
98
|
+
asset_id=str(arguments.get("asset_id") or ""),
|
|
99
|
+
focus=_optional_string(arguments.get("focus")),
|
|
100
|
+
max_chars=_optional_int(arguments.get("max_chars")),
|
|
101
|
+
)
|
|
102
|
+
if name == "asset_inspect":
|
|
103
|
+
return self.asset_inspect(
|
|
104
|
+
asset_id=str(arguments.get("asset_id") or ""),
|
|
105
|
+
angle=_optional_string(arguments.get("angle")),
|
|
106
|
+
)
|
|
107
|
+
return f"Unknown planner asset tool: {name}"
|
|
108
|
+
|
|
109
|
+
def asset_read(
|
|
110
|
+
self,
|
|
111
|
+
*,
|
|
112
|
+
asset_id: str,
|
|
113
|
+
focus: str | None = None,
|
|
114
|
+
max_chars: int | None = None,
|
|
115
|
+
) -> str:
|
|
116
|
+
result = perform_asset_read(
|
|
117
|
+
surface=self.surface,
|
|
118
|
+
asset_id=asset_id,
|
|
119
|
+
focus=focus,
|
|
120
|
+
max_chars=max_chars,
|
|
121
|
+
api_key=self.api_key,
|
|
122
|
+
cfg=self.cfg,
|
|
123
|
+
model_registry=self.model_registry,
|
|
124
|
+
transport=self.transport,
|
|
125
|
+
client_factory=make_llm_client,
|
|
126
|
+
)
|
|
127
|
+
return result.text
|
|
128
|
+
|
|
129
|
+
def asset_inspect(self, *, asset_id: str, angle: str | None = None) -> str:
|
|
130
|
+
count = self._inspect_counts.get(asset_id, 0)
|
|
131
|
+
if count >= _INSPECT_CAP_PER_ASSET:
|
|
132
|
+
return "Inspection cap reached for this asset; proceed with available data or ask the user."
|
|
133
|
+
self._inspect_counts[asset_id] = count + 1
|
|
134
|
+
try:
|
|
135
|
+
handle = self.surface.refresh_comprehension(
|
|
136
|
+
asset_id,
|
|
137
|
+
mode="sync",
|
|
138
|
+
angle=angle,
|
|
139
|
+
)
|
|
140
|
+
record = handle.join()
|
|
141
|
+
except AssetError as exc:
|
|
142
|
+
return f"Asset inspection failed: {exc}"
|
|
143
|
+
if record is None:
|
|
144
|
+
return "Asset inspection did not produce a comprehension record."
|
|
145
|
+
LOGGER.info(
|
|
146
|
+
"asset_inspect asset_id=%s angle=%s result_status=%s",
|
|
147
|
+
asset_id,
|
|
148
|
+
str(bool(angle)).lower(),
|
|
149
|
+
record.status,
|
|
150
|
+
)
|
|
151
|
+
return _comprehension_result(record)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _comprehension_result(record: ComprehensionRecord) -> str:
|
|
155
|
+
lines = [
|
|
156
|
+
f"Asset inspection complete: {record.asset_id}",
|
|
157
|
+
f"- status: {record.status}",
|
|
158
|
+
f"- source: {record.source}",
|
|
159
|
+
f"- detected_language: {record.detected_language or 'unknown'}",
|
|
160
|
+
f"- summary: {record.data.semantic_summary or '(none)'}",
|
|
161
|
+
]
|
|
162
|
+
if record.data.stated_facts:
|
|
163
|
+
lines.append("- stated_facts:")
|
|
164
|
+
lines.extend(f" - {fact}" for fact in record.data.stated_facts[:8])
|
|
165
|
+
if record.data.open_questions:
|
|
166
|
+
lines.append("- open_questions:")
|
|
167
|
+
lines.extend(f" - {question}" for question in record.data.open_questions[:8])
|
|
168
|
+
return "\n".join(lines)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _optional_string(value: Any) -> str | None:
|
|
172
|
+
if value is None:
|
|
173
|
+
return None
|
|
174
|
+
text = str(value).strip()
|
|
175
|
+
return text or None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _optional_int(value: Any) -> int | None:
|
|
179
|
+
if value is None:
|
|
180
|
+
return None
|
|
181
|
+
try:
|
|
182
|
+
return int(value)
|
|
183
|
+
except (TypeError, ValueError):
|
|
184
|
+
return None
|