aria-code 0.167.0__py3-none-win_amd64.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.
- aria_bots/__init__.py +7 -0
- aria_bots/channels/README.md +97 -0
- aria_bots/channels/__init__.py +1 -0
- aria_bots/channels/approvals.py +263 -0
- aria_bots/channels/conversation.py +280 -0
- aria_bots/channels/digest.py +186 -0
- aria_bots/channels/registry.py +95 -0
- aria_bots/engine.py +144 -0
- aria_bots/feishu_bot.py +1385 -0
- aria_bots/logistics/__init__.py +1 -0
- aria_bots/logistics/carriers.py +253 -0
- aria_bots/logistics/freight.py +154 -0
- aria_bots/logistics/inventory.py +242 -0
- aria_bots/logistics/mcp_server.py +156 -0
- aria_bots/logistics/records.py +119 -0
- aria_bots/logistics/tenancy.py +152 -0
- aria_bots/market.py +91 -0
- aria_bots/relay_client.py +326 -0
- aria_bots/telegram_bot.py +299 -0
- aria_code/__init__.py +111 -0
- aria_code/_bin/aria-native.exe +0 -0
- aria_code/_bin/aria-runtime.exe +0 -0
- aria_code/_version.py +20 -0
- aria_code/account_memory.py +296 -0
- aria_code/account_session.py +166 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +49 -0
- aria_code/agents/base.py +329 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +315 -0
- aria_code/agents/financial/fundamental.py +178 -0
- aria_code/agents/financial/macro.py +111 -0
- aria_code/agents/financial/news.py +231 -0
- aria_code/agents/financial/risk.py +146 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +277 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +392 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +168 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +500 -0
- aria_code/agents/video/__init__.py +16 -0
- aria_code/agents/video/director.py +376 -0
- aria_code/agents/video/qa_agent.py +143 -0
- aria_code/agents/video/script_doctor.py +112 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/navigation.schema.json +110 -0
- aria_code/agents/warehouse/task_agent.py +172 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +9 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/approvals.py +11 -0
- aria_code/apps/channels/conversation.py +11 -0
- aria_code/apps/channels/digest.py +11 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +11 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/app_server.py +656 -0
- aria_code/apps/cli/auto_update.py +125 -0
- aria_code/apps/cli/bootstrap.py +206 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/chat_turn.py +1327 -0
- aria_code/apps/cli/cloud_models.py +231 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +34 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +378 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +215 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
- aria_code/apps/cli/commands/broker_cmds.py +1236 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +189 -0
- aria_code/apps/cli/commands/core_cmds.py +2748 -0
- aria_code/apps/cli/commands/data_cmds.py +521 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/engineering_cmds.py +399 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +544 -0
- aria_code/apps/cli/commands/market_cmds.py +1419 -0
- aria_code/apps/cli/commands/market_context.py +432 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2374 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
- aria_code/apps/cli/commands/report.py +476 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +723 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
- aria_code/apps/cli/commands/workflow_cmds.py +629 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +158 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/conversation_policy.py +53 -0
- aria_code/apps/cli/deterministic.py +200 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/earnings_view.py +172 -0
- aria_code/apps/cli/endpoint_migration.py +64 -0
- aria_code/apps/cli/exec_events.py +255 -0
- aria_code/apps/cli/file_references.py +155 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +239 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
- aria_code/apps/cli/handlers/market_handlers.py +2570 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/headless.py +352 -0
- aria_code/apps/cli/helpers.py +326 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +310 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +305 -0
- aria_code/apps/cli/intent_shadow.py +72 -0
- aria_code/apps/cli/intent_signals.py +347 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +550 -0
- aria_code/apps/cli/main.py +39 -0
- aria_code/apps/cli/market_hours.py +100 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/mixin_binding.py +32 -0
- aria_code/apps/cli/model_catalog.py +652 -0
- aria_code/apps/cli/model_probe.py +169 -0
- aria_code/apps/cli/model_retirement.py +65 -0
- aria_code/apps/cli/native_branding.py +12 -0
- aria_code/apps/cli/native_bridge.py +118 -0
- aria_code/apps/cli/native_entry.py +58 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +78 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +71 -0
- aria_code/apps/cli/prompts/select.py +193 -0
- aria_code/apps/cli/prompts/system_prompts.py +562 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +775 -0
- aria_code/apps/cli/providers/chat_routing.py +139 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
- aria_code/apps/cli/providers/runtime_bridge.py +586 -0
- aria_code/apps/cli/providers/vertexai_stream.py +592 -0
- aria_code/apps/cli/release_download.py +159 -0
- aria_code/apps/cli/review_runner.py +111 -0
- aria_code/apps/cli/runtime_consumer.py +769 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_refresh.py +123 -0
- aria_code/apps/cli/session_store.py +125 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/task_isolation.py +211 -0
- aria_code/apps/cli/terminal_chrome.py +412 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +932 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +113 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +181 -0
- aria_code/apps/cli/tools/market_tools.py +626 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +656 -0
- aria_code/apps/cli/tools/write_tools.py +961 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/transactions.py +269 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +257 -0
- aria_code/apps/cli/updater.py +373 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1663 -0
- aria_code/apps/cli/web_login.py +136 -0
- aria_code/apps/cli/workflow_runner.py +127 -0
- aria_code/apps/cli/workspace_route.py +121 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +5244 -0
- aria_code/aria_daemon.py +1372 -0
- aria_code/aria_feishu_bot.py +16 -0
- aria_code/aria_mcp_server.py +61 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_telegram_bot.py +11 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +16 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1985 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +190 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_server.py +276 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sec_statements.py +217 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/doctor.py +860 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/_finance_core/__init__.py +5 -0
- aria_code/domain/_finance_core/backtest_engine.py +325 -0
- aria_code/domain/_finance_core/performance_stats.py +101 -0
- aria_code/domain/backtest_engine.py +75 -0
- aria_code/domain/backtest_report.py +488 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/performance_stats.py +6 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/behavior.py +113 -0
- aria_code/evals/gap.py +93 -0
- aria_code/evals/harness.py +949 -0
- aria_code/evals/review_probe.py +132 -0
- aria_code/evals/runner.py +425 -0
- aria_code/evals/scorecard.py +322 -0
- aria_code/evals/summary.py +125 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +459 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +744 -0
- aria_code/memory_manager.py +273 -0
- aria_code/model_capability.py +512 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +26 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +58 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +232 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +162 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1705 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +393 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +75 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +304 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +137 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +160 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/_paths.py +92 -0
- aria_code/packages/aria_tools/registry.py +129 -0
- aria_code/packages/quant_engine/__init__.py +33 -0
- aria_code/packages/quant_engine/_namespace.py +24 -0
- aria_code/packages/quant_engine/backtest/__init__.py +10 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +102 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +76 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/healthcare/__init__.py +40 -0
- aria_code/packs/logistics/__init__.py +171 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/google_video_provider.py +256 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/arthera_gateway.py +90 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +137 -0
- aria_code/providers/llm/openai_compat.py +350 -0
- aria_code/providers/llm/registry.py +457 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +549 -0
- aria_code/providers/ollama_models.py +79 -0
- aria_code/providers/openai_params.py +32 -0
- aria_code/providers/tool_messages.py +100 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/review_service.py +497 -0
- aria_code/runtime/__init__.py +176 -0
- aria_code/runtime/acceptance.py +632 -0
- aria_code/runtime/agent_loop.py +1851 -0
- aria_code/runtime/agent_profiles.py +75 -0
- aria_code/runtime/approval.py +71 -0
- aria_code/runtime/baseline.py +219 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +481 -0
- aria_code/runtime/consult.py +65 -0
- aria_code/runtime/contract.py +282 -0
- aria_code/runtime/delivery.py +432 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +167 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/native_index.py +153 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/processes.py +343 -0
- aria_code/runtime/project_graph.py +614 -0
- aria_code/runtime/repo_map.py +712 -0
- aria_code/runtime/research_bundle.py +105 -0
- aria_code/runtime/review.py +269 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +522 -0
- aria_code/runtime/scoped_runs.py +113 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/semantic_diff.py +171 -0
- aria_code/runtime/subagent.py +721 -0
- aria_code/runtime/symbol_edit.py +117 -0
- aria_code/runtime/task_brief.py +46 -0
- aria_code/runtime/task_context.py +145 -0
- aria_code/runtime/task_ledger.py +108 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/task_outcome.py +33 -0
- aria_code/runtime/task_verification.py +266 -0
- aria_code/runtime/task_worktree.py +448 -0
- aria_code/runtime/tool_executor.py +329 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/transactions.py +216 -0
- aria_code/runtime/windows_job.py +115 -0
- aria_code/runtime/workflows.py +222 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +27 -0
- aria_code/safety/client_isolation.py +178 -0
- aria_code/safety/permissions.py +339 -0
- aria_code/safety/risk.py +699 -0
- aria_code/safety/sandbox.py +148 -0
- aria_code/safety/service.py +102 -0
- aria_code/service_endpoints.py +14 -0
- aria_code/setup_wizard.py +684 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +509 -0
- aria_code/tools/data_analysis_tools.py +831 -0
- aria_code/tools/enterprise_finance_tools.py +202 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +880 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3488 -0
- aria_code/tools/logistics_carriers.py +11 -0
- aria_code/tools/logistics_inventory.py +11 -0
- aria_code/tools/logistics_io.py +11 -0
- aria_code/tools/logistics_tenancy.py +11 -0
- aria_code/tools/logistics_tools.py +11 -0
- aria_code/tools/macro_tools.py +412 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +662 -0
- aria_code/tools/spreadsheet_tools.py +399 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/tools/video_tools.py +500 -0
- aria_code/training/__init__.py +1 -0
- aria_code/training/compare.py +117 -0
- aria_code/training/prelude_commands.py +111 -0
- aria_code/training/prelude_eval.py +390 -0
- aria_code/training/prelude_real.py +183 -0
- aria_code/training/prelude_train.py +248 -0
- aria_code/training/sft_dataset.py +230 -0
- aria_code/training/trajectory_dataset.py +263 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +404 -0
- aria_code/ui/completer.py +571 -0
- aria_code/ui/console.py +285 -0
- aria_code/ui/input_box.py +505 -0
- aria_code/ui/mentions.py +206 -0
- aria_code/ui/picker.py +248 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/actions.py +297 -0
- aria_code/ui/render/finance.py +1732 -0
- aria_code/ui/render/market.py +253 -0
- aria_code/ui/render/output.py +954 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +367 -0
- aria_code/ui/robot.py +239 -0
- aria_code/ui/startup_dashboard.py +110 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +518 -0
- aria_code/video_workflow/__init__.py +8 -0
- aria_code/video_workflow/__main__.py +61 -0
- aria_code/video_workflow/engines/__init__.py +18 -0
- aria_code/video_workflow/engines/base.py +69 -0
- aria_code/video_workflow/engines/custom_model.py +204 -0
- aria_code/video_workflow/engines/google_veo.py +94 -0
- aria_code/video_workflow/engines/local_engine.py +71 -0
- aria_code/video_workflow/engines/registry.py +98 -0
- aria_code/video_workflow/google_models.py +278 -0
- aria_code/video_workflow/music/__init__.py +5 -0
- aria_code/video_workflow/music/foley_service.py +132 -0
- aria_code/video_workflow/music/service.py +212 -0
- aria_code/video_workflow/pipeline.py +620 -0
- aria_code/video_workflow/plan.py +202 -0
- aria_code/video_workflow/session.py +297 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +219 -0
- aria_code/workspace/verify.py +167 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.167.0.dist-info/METADATA +330 -0
- aria_code-0.167.0.dist-info/RECORD +601 -0
- aria_code-0.167.0.dist-info/WHEEL +5 -0
- aria_code-0.167.0.dist-info/entry_points.txt +5 -0
- aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.167.0.dist-info/top_level.txt +2 -0
aria_code/safety/risk.py
ADDED
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
"""Risk assessment for agent actions: what an action can touch, and how badly.
|
|
2
|
+
|
|
3
|
+
``classify_command_risk`` answers one question — low, medium or high — and the
|
|
4
|
+
policy layer decides from that whether a command may run. That is enough to
|
|
5
|
+
gate, not enough to explain. "Allow `npm install jose`? [y/N]" asks a person to
|
|
6
|
+
judge a command; what they actually need to judge is its consequences: it
|
|
7
|
+
reaches the npm registry, it rewrites package.json and the lockfile, and it can
|
|
8
|
+
be undone. This module turns an action into that description.
|
|
9
|
+
|
|
10
|
+
An assessment carries:
|
|
11
|
+
|
|
12
|
+
- a **level**, L0–L4, the unit approvals are decided in:
|
|
13
|
+
|
|
14
|
+
==== =============================== ===============================
|
|
15
|
+
L0 read / inspect / verify ls, cat, rg, git diff, pytest
|
|
16
|
+
L1 change inside the workspace edit a source file, git add
|
|
17
|
+
L2 change the environment npm install, pip install, curl
|
|
18
|
+
L3 act outside the machine's repo git commit/push, gh pr create
|
|
19
|
+
L4 destructive or production rm, force push, migrate, deploy,
|
|
20
|
+
secrets, sudo
|
|
21
|
+
==== =============================== ===============================
|
|
22
|
+
|
|
23
|
+
- a **score** (0–100) for ordering and display within a level;
|
|
24
|
+
- the **capabilities** used (``network.read``, ``package.install``,
|
|
25
|
+
``git.push`` …) — finer than a permission mode, and what a Change Contract
|
|
26
|
+
names when it forbids something;
|
|
27
|
+
- whether it is **reversible**, the **reasons**, and its **scope** (hosts,
|
|
28
|
+
files, systems) — the blast radius.
|
|
29
|
+
|
|
30
|
+
It never lowers what the existing policy says: a command
|
|
31
|
+
``classify_command_risk`` calls high is at least L3 here. Assessment is
|
|
32
|
+
description; whether to run stays with ``evaluate_command_policy``.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import re
|
|
38
|
+
import shlex
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from typing import Any, Iterable, Mapping, Optional
|
|
42
|
+
from urllib.parse import urlparse
|
|
43
|
+
|
|
44
|
+
from .permissions import (
|
|
45
|
+
classify_command_risk,
|
|
46
|
+
has_shell_control,
|
|
47
|
+
is_verification_command,
|
|
48
|
+
normalize_command,
|
|
49
|
+
writes_a_device,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
LEVEL_NAMES = {0: "none", 1: "low", 2: "medium", 3: "high", 4: "critical"}
|
|
53
|
+
_BASE_SCORE = {0: 5, 1: 20, 2: 40, 3: 65, 4: 85}
|
|
54
|
+
|
|
55
|
+
# Every capability an assessment can name. A contract that forbids one not in
|
|
56
|
+
# this set is a typo, and is reported as one.
|
|
57
|
+
CAPABILITIES = frozenset({
|
|
58
|
+
"filesystem.read", "filesystem.write", "filesystem.delete",
|
|
59
|
+
"process.execute",
|
|
60
|
+
"network.read", "network.write",
|
|
61
|
+
"package.install",
|
|
62
|
+
"git.stage", "git.commit", "git.push", "git.history",
|
|
63
|
+
"github.pr",
|
|
64
|
+
"database.read", "database.write",
|
|
65
|
+
"secret.read",
|
|
66
|
+
"cloud.deploy",
|
|
67
|
+
"system.admin",
|
|
68
|
+
# Physical: acting on a device or a robot, which cannot be undone with a checkpoint.
|
|
69
|
+
"hardware.flash", "actuator.command",
|
|
70
|
+
# The live desktop: reading the screen and driving the mouse and keyboard.
|
|
71
|
+
"screen.capture", "desktop.control",
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class RiskAssessment:
|
|
77
|
+
level: int
|
|
78
|
+
score: int
|
|
79
|
+
capabilities: frozenset = frozenset()
|
|
80
|
+
reversible: bool = True
|
|
81
|
+
reasons: tuple = ()
|
|
82
|
+
# Blast radius: what the action reaches, by kind.
|
|
83
|
+
hosts: tuple = ()
|
|
84
|
+
files: tuple = ()
|
|
85
|
+
systems: tuple = ()
|
|
86
|
+
summary: str = ""
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def name(self) -> str:
|
|
90
|
+
return LEVEL_NAMES[self.level]
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def legacy_risk(self) -> str:
|
|
94
|
+
"""The low/medium/high the older policy layer speaks."""
|
|
95
|
+
return "low" if self.level == 0 else "high" if self.level >= 3 else "medium"
|
|
96
|
+
|
|
97
|
+
def as_dict(self) -> dict:
|
|
98
|
+
return {
|
|
99
|
+
"level": self.level,
|
|
100
|
+
"name": self.name,
|
|
101
|
+
"score": self.score,
|
|
102
|
+
"capabilities": sorted(self.capabilities),
|
|
103
|
+
"reversible": self.reversible,
|
|
104
|
+
"reasons": list(self.reasons),
|
|
105
|
+
"hosts": list(self.hosts),
|
|
106
|
+
"files": list(self.files),
|
|
107
|
+
"systems": list(self.systems),
|
|
108
|
+
"summary": self.summary,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class _Draft:
|
|
114
|
+
level: int = 0
|
|
115
|
+
capabilities: set = field(default_factory=set)
|
|
116
|
+
reversible: bool = True
|
|
117
|
+
reasons: list = field(default_factory=list)
|
|
118
|
+
hosts: list = field(default_factory=list)
|
|
119
|
+
files: list = field(default_factory=list)
|
|
120
|
+
systems: list = field(default_factory=list)
|
|
121
|
+
summary: str = ""
|
|
122
|
+
|
|
123
|
+
def raise_to(self, level: int, reason: str = "", *, reversible: Optional[bool] = None) -> None:
|
|
124
|
+
self.level = max(self.level, level)
|
|
125
|
+
if reason and reason not in self.reasons:
|
|
126
|
+
self.reasons.append(reason)
|
|
127
|
+
if reversible is False:
|
|
128
|
+
self.reversible = False
|
|
129
|
+
|
|
130
|
+
def merge(self, other: "_Draft") -> None:
|
|
131
|
+
self.level = max(self.level, other.level)
|
|
132
|
+
self.capabilities |= other.capabilities
|
|
133
|
+
self.reversible = self.reversible and other.reversible
|
|
134
|
+
for mine, theirs in ((self.reasons, other.reasons), (self.hosts, other.hosts),
|
|
135
|
+
(self.files, other.files), (self.systems, other.systems)):
|
|
136
|
+
mine.extend(item for item in theirs if item not in mine)
|
|
137
|
+
self.summary = self.summary or other.summary
|
|
138
|
+
|
|
139
|
+
def freeze(self, *, extra: int = 0) -> RiskAssessment:
|
|
140
|
+
score = _BASE_SCORE[self.level] + extra
|
|
141
|
+
if not self.reversible:
|
|
142
|
+
score += 8
|
|
143
|
+
if "network.write" in self.capabilities or "network.read" in self.capabilities:
|
|
144
|
+
score += 3
|
|
145
|
+
return RiskAssessment(
|
|
146
|
+
level=self.level,
|
|
147
|
+
score=max(0, min(100, score)),
|
|
148
|
+
capabilities=frozenset(self.capabilities),
|
|
149
|
+
reversible=self.reversible,
|
|
150
|
+
reasons=tuple(self.reasons),
|
|
151
|
+
hosts=tuple(self.hosts),
|
|
152
|
+
files=tuple(self.files),
|
|
153
|
+
systems=tuple(self.systems),
|
|
154
|
+
summary=self.summary,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ── commands ──────────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
_SEGMENTS = re.compile(r"\|\||&&|[|;&\n]")
|
|
161
|
+
_READ_COMMANDS = {
|
|
162
|
+
"ls", "pwd", "echo", "cat", "head", "tail", "less", "more", "wc", "rg", "grep", "find",
|
|
163
|
+
"fd", "tree", "which", "whoami", "date", "uname", "file", "stat", "du", "df", "diff",
|
|
164
|
+
"sort", "uniq", "cut", "awk", "sed", "jq", "true", "false", "test", "basename", "dirname",
|
|
165
|
+
}
|
|
166
|
+
_GIT_READ = {"status", "diff", "log", "show", "blame", "branch", "rev-parse", "ls-files",
|
|
167
|
+
"remote", "describe", "shortlog", "grep", "config"}
|
|
168
|
+
_INSTALLERS = {
|
|
169
|
+
# tool -> (subcommands that install, files rewritten, registry host)
|
|
170
|
+
"npm": ({"install", "i", "add", "ci", "update", "uninstall", "remove"},
|
|
171
|
+
("package.json", "package-lock.json"), "registry.npmjs.org"),
|
|
172
|
+
"pnpm": ({"install", "i", "add", "update", "remove"}, ("package.json", "pnpm-lock.yaml"),
|
|
173
|
+
"registry.npmjs.org"),
|
|
174
|
+
"yarn": ({"install", "add", "upgrade", "remove", ""}, ("package.json", "yarn.lock"),
|
|
175
|
+
"registry.yarnpkg.com"),
|
|
176
|
+
"bun": ({"install", "add", "i", "remove"}, ("package.json", "bun.lockb"), "registry.npmjs.org"),
|
|
177
|
+
"pip": ({"install", "uninstall"}, (), "pypi.org"),
|
|
178
|
+
"pip3": ({"install", "uninstall"}, (), "pypi.org"),
|
|
179
|
+
"uv": ({"add", "pip", "sync", "remove"}, ("pyproject.toml", "uv.lock"), "pypi.org"),
|
|
180
|
+
"poetry": ({"add", "install", "update", "remove"}, ("pyproject.toml", "poetry.lock"), "pypi.org"),
|
|
181
|
+
"cargo": ({"add", "install", "update", "remove"}, ("Cargo.toml", "Cargo.lock"), "crates.io"),
|
|
182
|
+
"go": ({"get", "install"}, ("go.mod", "go.sum"), "proxy.golang.org"),
|
|
183
|
+
"gem": ({"install", "uninstall"}, (), "rubygems.org"),
|
|
184
|
+
"bundle": ({"install", "add", "update"}, ("Gemfile", "Gemfile.lock"), "rubygems.org"),
|
|
185
|
+
"brew": ({"install", "uninstall", "upgrade"}, (), "formulae.brew.sh"),
|
|
186
|
+
"apt": ({"install", "remove"}, (), "deb.debian.org"),
|
|
187
|
+
"apt-get": ({"install", "remove"}, (), "deb.debian.org"),
|
|
188
|
+
}
|
|
189
|
+
_DEPLOY = (
|
|
190
|
+
re.compile(r"^vercel\b.*--prod"), re.compile(r"^(fly|flyctl) deploy\b"),
|
|
191
|
+
re.compile(r"^netlify deploy\b.*--prod"), re.compile(r"^firebase deploy\b"),
|
|
192
|
+
re.compile(r"^gcloud (app|run|functions) deploy\b"), re.compile(r"^terraform (apply|destroy)\b"),
|
|
193
|
+
re.compile(r"^pulumi (up|destroy)\b"), re.compile(r"^kubectl (apply|delete|rollout|scale)\b"),
|
|
194
|
+
re.compile(r"^helm (install|upgrade|uninstall)\b"), re.compile(r"^docker push\b"),
|
|
195
|
+
re.compile(r"^(npm|pnpm|yarn) publish\b"), re.compile(r"^twine upload\b"),
|
|
196
|
+
re.compile(r"^cargo publish\b"), re.compile(r"^aws .*\b(deploy|update-function-code|s3 (rm|sync|cp))\b"),
|
|
197
|
+
re.compile(r"^serverless deploy\b"), re.compile(r"^sls deploy\b"),
|
|
198
|
+
)
|
|
199
|
+
_MIGRATIONS = (
|
|
200
|
+
re.compile(r"\bmigrate\b"), re.compile(r"\balembic (upgrade|downgrade)\b"),
|
|
201
|
+
re.compile(r"\bprisma (migrate|db push)\b"), re.compile(r"\bdb:(migrate|rollback|drop|reset)\b"),
|
|
202
|
+
re.compile(r"\bknex migrate\b"), re.compile(r"\bflyway migrate\b"),
|
|
203
|
+
)
|
|
204
|
+
# Programmers that write firmware to a device: (command, the words that mean "write").
|
|
205
|
+
_FLASHERS = {
|
|
206
|
+
"esptool": {"write_flash", "write-flash", "erase_flash", "erase-flash", "erase_region", "erase-region"},
|
|
207
|
+
"esptool.py": {"write_flash", "write-flash", "erase_flash", "erase-flash", "erase_region", "erase-region"},
|
|
208
|
+
"dfu-util": {"-D", "--download"},
|
|
209
|
+
"st-flash": {"write", "erase"},
|
|
210
|
+
"stm32flash": {"-w"},
|
|
211
|
+
"nrfjprog": {"--program", "--eraseall", "--recover"},
|
|
212
|
+
"picotool": {"load", "erase"},
|
|
213
|
+
"fastboot": {"flash", "flashall", "erase", "update"},
|
|
214
|
+
"arduino-cli": {"upload"},
|
|
215
|
+
"pio": {"upload"},
|
|
216
|
+
"platformio": {"upload"},
|
|
217
|
+
"idf.py": {"flash", "erase-flash", "erase_flash"},
|
|
218
|
+
"west": {"flash"},
|
|
219
|
+
}
|
|
220
|
+
def _writes_firmware(head: str, words: list, lowered: str) -> bool:
|
|
221
|
+
"""True for a programmer invoked to write or erase a device, not to read it."""
|
|
222
|
+
if head == "openocd":
|
|
223
|
+
return bool(re.search(r"\b(program|flash write_image)\b", lowered))
|
|
224
|
+
if head == "avrdude":
|
|
225
|
+
return bool(re.search(r"flash:w", lowered))
|
|
226
|
+
verbs = _FLASHERS.get(head)
|
|
227
|
+
return verbs is not None and any(word in verbs for word in words[1:])
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# `ros2 <noun> <verb>` that send a command to a running robot.
|
|
231
|
+
_ROS_ACTUATION = {("topic", "pub"), ("service", "call"), ("action", "send_goal"),
|
|
232
|
+
("param", "set"), ("lifecycle", "set")}
|
|
233
|
+
_SQL_WRITE = re.compile(r"\b(insert|update|delete|drop|alter|truncate|create)\b", re.IGNORECASE)
|
|
234
|
+
_DB_CLIENTS = {"psql", "mysql", "sqlite3", "mongosh", "mongo", "redis-cli", "cqlsh"}
|
|
235
|
+
_SECRET_PATH = re.compile(
|
|
236
|
+
r"(^|[\s/'\"])(\.env(\.[\w-]+)?|\.npmrc|\.pypirc|\.netrc|id_rsa|id_ed25519|"
|
|
237
|
+
r"[\w.-]+\.pem|[\w.-]+\.key|credentials(\.json)?|\.aws/credentials|\.ssh/)(\s|$|['\"])"
|
|
238
|
+
)
|
|
239
|
+
_URL = re.compile(r"https?://[^\s'\"]+")
|
|
240
|
+
_PIPE_TO_SHELL = re.compile(r"\b(curl|wget)\b[^|;&]*\|\s*(sudo\s+)?(ba|z|da)?sh\b")
|
|
241
|
+
_HTTP_WRITE = re.compile(r"(\s-X\s*(POST|PUT|PATCH|DELETE)\b|\s--data\b|\s-d\s|\s--data-\w+|\s-F\s|\s--form\b)",
|
|
242
|
+
re.IGNORECASE)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _words(segment: str) -> list[str]:
|
|
246
|
+
try:
|
|
247
|
+
return shlex.split(segment)
|
|
248
|
+
except ValueError:
|
|
249
|
+
return segment.split()
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _hosts(segment: str) -> list[str]:
|
|
253
|
+
hosts = []
|
|
254
|
+
for url in _URL.findall(segment):
|
|
255
|
+
host = urlparse(url).hostname
|
|
256
|
+
if host and host not in hosts:
|
|
257
|
+
hosts.append(host)
|
|
258
|
+
return hosts
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _targets(words: list[str]) -> list[str]:
|
|
262
|
+
return [w for w in words[1:] if not w.startswith("-")]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _assess_segment(segment: str) -> _Draft:
|
|
266
|
+
draft = _Draft()
|
|
267
|
+
words = _words(segment)
|
|
268
|
+
if not words:
|
|
269
|
+
return draft
|
|
270
|
+
# `VAR=1 cmd`, `sudo cmd`, `time cmd`: judge the command, note the wrapper.
|
|
271
|
+
while words and re.match(r"^\w+=", words[0]):
|
|
272
|
+
words = words[1:]
|
|
273
|
+
if words and words[0] == "sudo":
|
|
274
|
+
draft.capabilities.add("system.admin")
|
|
275
|
+
draft.raise_to(4, "Runs with administrator privileges", reversible=False)
|
|
276
|
+
draft.systems.append("Operating system")
|
|
277
|
+
words = words[1:]
|
|
278
|
+
while words and words[0] in {"time", "nice", "nohup", "env"} and len(words) > 1:
|
|
279
|
+
words = words[1:]
|
|
280
|
+
if not words:
|
|
281
|
+
return draft
|
|
282
|
+
head = Path(words[0]).name
|
|
283
|
+
sub = words[1] if len(words) > 1 else ""
|
|
284
|
+
lowered = segment.lower().strip()
|
|
285
|
+
draft.capabilities.add("process.execute")
|
|
286
|
+
|
|
287
|
+
if _SECRET_PATH.search(" " + segment + " ") and head in _READ_COMMANDS | {"source", "."}:
|
|
288
|
+
draft.capabilities.add("secret.read")
|
|
289
|
+
draft.raise_to(4, "Reads a credentials or secrets file", reversible=False)
|
|
290
|
+
draft.summary = "Read secrets"
|
|
291
|
+
return draft
|
|
292
|
+
if head in {"env", "printenv"}:
|
|
293
|
+
draft.capabilities.add("secret.read")
|
|
294
|
+
draft.raise_to(3, "Prints environment variables, which may hold secrets")
|
|
295
|
+
draft.summary = "Print environment"
|
|
296
|
+
return draft
|
|
297
|
+
|
|
298
|
+
if head == "git":
|
|
299
|
+
return _assess_git(words, draft)
|
|
300
|
+
if head == "gh":
|
|
301
|
+
return _assess_gh(words, draft)
|
|
302
|
+
if head in _INSTALLERS:
|
|
303
|
+
installs, files, host = _INSTALLERS[head]
|
|
304
|
+
if sub in installs:
|
|
305
|
+
draft.capabilities |= {"package.install", "network.read"}
|
|
306
|
+
draft.raise_to(2, f"Changes installed dependencies ({head} {sub})".strip())
|
|
307
|
+
draft.hosts.append(host)
|
|
308
|
+
draft.files.extend(files)
|
|
309
|
+
if not files:
|
|
310
|
+
draft.systems.append("Python environment" if head.startswith("pip") else "System packages"
|
|
311
|
+
if head in {"brew", "apt", "apt-get"} else f"{head} environment")
|
|
312
|
+
draft.summary = "Install dependency"
|
|
313
|
+
return draft
|
|
314
|
+
if _writes_firmware(head, words, lowered):
|
|
315
|
+
draft.capabilities.add("hardware.flash")
|
|
316
|
+
draft.raise_to(4, "Writes firmware to a device; a bad image can leave it unusable", reversible=False)
|
|
317
|
+
draft.systems.append("Device firmware")
|
|
318
|
+
draft.summary = "Flash firmware"
|
|
319
|
+
return draft
|
|
320
|
+
if head == "ros2" and len(words) >= 3 and (words[1], words[2]) in _ROS_ACTUATION:
|
|
321
|
+
draft.capabilities.add("actuator.command")
|
|
322
|
+
draft.raise_to(4, f"Sends a command to a running robot (ros2 {words[1]} {words[2]}); it may move hardware",
|
|
323
|
+
reversible=False)
|
|
324
|
+
draft.systems.append("Robot / actuators")
|
|
325
|
+
draft.summary = "Command a robot"
|
|
326
|
+
return draft
|
|
327
|
+
for pattern in _DEPLOY:
|
|
328
|
+
if pattern.search(lowered):
|
|
329
|
+
draft.capabilities |= {"cloud.deploy", "network.write"}
|
|
330
|
+
draft.raise_to(4, "Deploys or publishes outside this machine", reversible=False)
|
|
331
|
+
draft.systems.append("Production / remote environment")
|
|
332
|
+
draft.summary = "Deploy or publish"
|
|
333
|
+
return draft
|
|
334
|
+
if any(pattern.search(lowered) for pattern in _MIGRATIONS):
|
|
335
|
+
draft.capabilities.add("database.write")
|
|
336
|
+
draft.raise_to(4, "Runs a database migration", reversible=False)
|
|
337
|
+
draft.systems.append("Database schema")
|
|
338
|
+
draft.summary = "Database migration"
|
|
339
|
+
return draft
|
|
340
|
+
if head in _DB_CLIENTS:
|
|
341
|
+
if _SQL_WRITE.search(segment):
|
|
342
|
+
draft.capabilities.add("database.write")
|
|
343
|
+
draft.raise_to(4, "Writes to a database", reversible=False)
|
|
344
|
+
draft.systems.append("Database")
|
|
345
|
+
draft.summary = "Database write"
|
|
346
|
+
else:
|
|
347
|
+
draft.capabilities.add("database.read")
|
|
348
|
+
draft.raise_to(2, "Connects to a database")
|
|
349
|
+
draft.systems.append("Database")
|
|
350
|
+
draft.summary = "Database query"
|
|
351
|
+
return draft
|
|
352
|
+
if head in {"curl", "wget", "http", "https", "httpie", "xh"}:
|
|
353
|
+
hosts = _hosts(segment)
|
|
354
|
+
draft.hosts.extend(hosts)
|
|
355
|
+
if head in {"curl", "http", "https", "httpie", "xh"} and _HTTP_WRITE.search(" " + segment + " "):
|
|
356
|
+
draft.capabilities.add("network.write")
|
|
357
|
+
draft.raise_to(3, "Sends data to a remote server", reversible=False)
|
|
358
|
+
draft.summary = "Network write"
|
|
359
|
+
else:
|
|
360
|
+
draft.capabilities.add("network.read")
|
|
361
|
+
draft.raise_to(2, "Fetches from the network")
|
|
362
|
+
draft.summary = "Network request"
|
|
363
|
+
return draft
|
|
364
|
+
if head == "rm" or head == "rmdir" or head == "shred" or head == "unlink":
|
|
365
|
+
draft.capabilities.add("filesystem.delete")
|
|
366
|
+
targets = _targets(words)
|
|
367
|
+
draft.files.extend(targets)
|
|
368
|
+
draft.raise_to(4, "Deletes files without a checkpoint", reversible=False)
|
|
369
|
+
if any(t in {"/", "~", "*", ".", ".."} or t.startswith(("/", "~")) for t in targets):
|
|
370
|
+
draft.raise_to(4, "Deletes outside the workspace or a whole tree")
|
|
371
|
+
draft.summary = "Delete files"
|
|
372
|
+
return draft
|
|
373
|
+
if head in {"mv", "cp", "touch", "mkdir", "ln", "tee"}:
|
|
374
|
+
draft.capabilities.add("filesystem.write")
|
|
375
|
+
draft.files.extend(_targets(words)[-1:])
|
|
376
|
+
draft.raise_to(1, "Changes files in the workspace")
|
|
377
|
+
draft.summary = "Change files"
|
|
378
|
+
return draft
|
|
379
|
+
if head in {"chmod", "chown", "mkfs", "dd", "shutdown", "reboot", "systemctl", "launchctl", "passwd", "kill",
|
|
380
|
+
"killall", "pkill", "docker", "kubectl"}:
|
|
381
|
+
draft.capabilities.add("system.admin")
|
|
382
|
+
draft.raise_to(3 if head in {"kill", "pkill", "killall", "docker"} else 4,
|
|
383
|
+
"Changes system state outside the workspace", reversible=False)
|
|
384
|
+
draft.systems.append("Operating system")
|
|
385
|
+
draft.summary = "System change"
|
|
386
|
+
return draft
|
|
387
|
+
if is_verification_command(segment):
|
|
388
|
+
draft.capabilities.add("filesystem.read")
|
|
389
|
+
draft.summary = "Verify"
|
|
390
|
+
return draft
|
|
391
|
+
if head in _READ_COMMANDS:
|
|
392
|
+
if head == "sed" and re.search(r"\s-i\b", segment):
|
|
393
|
+
draft.capabilities.add("filesystem.write")
|
|
394
|
+
draft.raise_to(1, "Edits files in place")
|
|
395
|
+
draft.files.extend(_targets(words)[-1:])
|
|
396
|
+
draft.summary = "Change files"
|
|
397
|
+
else:
|
|
398
|
+
draft.capabilities.add("filesystem.read")
|
|
399
|
+
draft.summary = "Inspect"
|
|
400
|
+
return draft
|
|
401
|
+
# A program we cannot see into: it runs code and may write.
|
|
402
|
+
draft.capabilities |= {"filesystem.write"}
|
|
403
|
+
draft.raise_to(1, f"Runs {head}, which may change files in the workspace")
|
|
404
|
+
draft.summary = f"Run {head}"
|
|
405
|
+
return draft
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _assess_git(words: list[str], draft: _Draft) -> _Draft:
|
|
409
|
+
args = [w for w in words[1:] if not w.startswith("-C")]
|
|
410
|
+
sub = next((w for w in args if not w.startswith("-")), "")
|
|
411
|
+
flags = set(words)
|
|
412
|
+
if sub in _GIT_READ:
|
|
413
|
+
draft.capabilities.add("filesystem.read")
|
|
414
|
+
draft.summary = "Inspect repository"
|
|
415
|
+
return draft
|
|
416
|
+
if sub in {"fetch", "pull", "clone"}:
|
|
417
|
+
draft.capabilities |= {"network.read", "git.history"} if sub != "clone" else {"network.read",
|
|
418
|
+
"filesystem.write"}
|
|
419
|
+
draft.raise_to(2, f"git {sub} reaches a remote")
|
|
420
|
+
draft.summary = f"git {sub}"
|
|
421
|
+
return draft
|
|
422
|
+
if sub in {"add", "rm", "mv", "restore", "stash", "switch", "checkout", "worktree"}:
|
|
423
|
+
draft.capabilities.add("git.stage")
|
|
424
|
+
draft.raise_to(1, f"git {sub} changes the working tree or index")
|
|
425
|
+
discards = (sub == "checkout" and "--" in words) or (sub == "restore" and "--staged" not in flags)
|
|
426
|
+
if discards:
|
|
427
|
+
draft.raise_to(4, "Discards uncommitted changes", reversible=False)
|
|
428
|
+
draft.summary = f"git {sub}"
|
|
429
|
+
return draft
|
|
430
|
+
if sub in {"commit", "merge", "rebase", "cherry-pick", "revert", "tag", "am"}:
|
|
431
|
+
draft.capabilities.add("git.commit")
|
|
432
|
+
draft.raise_to(3, f"git {sub} writes repository history")
|
|
433
|
+
draft.summary = f"git {sub}"
|
|
434
|
+
return draft
|
|
435
|
+
if sub == "push":
|
|
436
|
+
draft.capabilities |= {"git.push", "network.write"}
|
|
437
|
+
draft.hosts.append("git remote")
|
|
438
|
+
if flags & {"--force", "-f", "--force-with-lease", "--mirror", "--delete", "-d"} or any(
|
|
439
|
+
w.startswith("+") for w in args[1:]):
|
|
440
|
+
draft.raise_to(4, "Rewrites or deletes history on the remote", reversible=False)
|
|
441
|
+
draft.summary = "Force push"
|
|
442
|
+
else:
|
|
443
|
+
draft.raise_to(3, "Publishes commits to the remote", reversible=False)
|
|
444
|
+
draft.summary = "git push"
|
|
445
|
+
return draft
|
|
446
|
+
if sub in {"reset", "clean"}:
|
|
447
|
+
draft.capabilities.add("git.history")
|
|
448
|
+
if "--hard" in flags or sub == "clean":
|
|
449
|
+
draft.raise_to(4, "Discards uncommitted work", reversible=False)
|
|
450
|
+
else:
|
|
451
|
+
draft.raise_to(1, "Moves the index")
|
|
452
|
+
draft.summary = f"git {sub}"
|
|
453
|
+
return draft
|
|
454
|
+
draft.capabilities.add("git.history")
|
|
455
|
+
draft.raise_to(1, f"git {sub or 'command'}")
|
|
456
|
+
draft.summary = f"git {sub}".strip()
|
|
457
|
+
return draft
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _assess_gh(words: list[str], draft: _Draft) -> _Draft:
|
|
461
|
+
sub = words[1] if len(words) > 1 else ""
|
|
462
|
+
action = words[2] if len(words) > 2 else ""
|
|
463
|
+
draft.hosts.append("github.com")
|
|
464
|
+
if sub == "pr" and action in {"create", "merge", "close", "edit", "comment", "review", "ready"}:
|
|
465
|
+
draft.capabilities |= {"github.pr", "network.write"}
|
|
466
|
+
draft.raise_to(4 if action == "merge" else 3, f"gh pr {action} acts on GitHub", reversible=action != "merge")
|
|
467
|
+
draft.summary = f"gh pr {action}"
|
|
468
|
+
return draft
|
|
469
|
+
if sub in {"release", "repo", "secret", "workflow", "api"} and action not in {"view", "list", "status", ""}:
|
|
470
|
+
draft.capabilities.add("network.write")
|
|
471
|
+
draft.raise_to(4 if sub in {"secret", "release", "repo"} else 3, f"gh {sub} {action} changes GitHub",
|
|
472
|
+
reversible=False)
|
|
473
|
+
draft.summary = f"gh {sub} {action}"
|
|
474
|
+
return draft
|
|
475
|
+
draft.capabilities.add("network.read")
|
|
476
|
+
draft.raise_to(2, "Reads from GitHub")
|
|
477
|
+
draft.summary = f"gh {sub}".strip()
|
|
478
|
+
return draft
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def assess_command(command: Any) -> RiskAssessment:
|
|
482
|
+
"""What running *command* touches, and how badly. Never raises."""
|
|
483
|
+
normalized = normalize_command(command)
|
|
484
|
+
if not normalized:
|
|
485
|
+
return _Draft().freeze()
|
|
486
|
+
draft = _Draft()
|
|
487
|
+
segments = [s.strip() for s in _SEGMENTS.split(normalized) if s.strip()] or [normalized]
|
|
488
|
+
for segment in segments:
|
|
489
|
+
draft.merge(_assess_segment(segment))
|
|
490
|
+
if len(segments) > 1:
|
|
491
|
+
draft.summary = draft.summary or "Compound command"
|
|
492
|
+
# Segments are judged one by one, so this has to look across the pipe.
|
|
493
|
+
if _PIPE_TO_SHELL.search(normalized):
|
|
494
|
+
draft.capabilities.add("network.read")
|
|
495
|
+
draft.raise_to(4, "Runs a downloaded script without reading it", reversible=False)
|
|
496
|
+
draft.summary = "Download and run"
|
|
497
|
+
if writes_a_device(normalized):
|
|
498
|
+
draft.capabilities.add("system.admin")
|
|
499
|
+
draft.raise_to(4, "Writes to a device", reversible=False)
|
|
500
|
+
if re.search(r">>?\s*[^&\s]", normalized) and "filesystem.write" not in draft.capabilities:
|
|
501
|
+
draft.capabilities.add("filesystem.write")
|
|
502
|
+
draft.raise_to(1, "Redirects output into a file")
|
|
503
|
+
# Never describe as safer than the gate treats it.
|
|
504
|
+
legacy = classify_command_risk(normalized)
|
|
505
|
+
if legacy == "high" and draft.level < 3:
|
|
506
|
+
draft.raise_to(3, "Classified high-risk by the command policy")
|
|
507
|
+
if legacy == "medium" and draft.level == 0 and not is_verification_command(normalized):
|
|
508
|
+
draft.raise_to(1)
|
|
509
|
+
extra = 4 if has_shell_control(normalized) and len(segments) > 1 else 0
|
|
510
|
+
return draft.freeze(extra=extra)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# ── tools ─────────────────────────────────────────────────────────────────────
|
|
514
|
+
|
|
515
|
+
READ_TOOLS = frozenset({
|
|
516
|
+
"read_file", "list_files", "search_code", "glob", "project_context", "git_status", "git_diff",
|
|
517
|
+
"repo_map", "find_symbol", "analyze_file", "notebook_read", "update_todos",
|
|
518
|
+
})
|
|
519
|
+
WRITE_TOOLS = frozenset({
|
|
520
|
+
"write_file", "edit_file", "multi_edit", "apply_change", "apply_patch", "notebook_edit",
|
|
521
|
+
})
|
|
522
|
+
NETWORK_READ_TOOLS = frozenset({
|
|
523
|
+
"web_fetch", "web_search", "get_market_data", "get_market_history", "browser_navigate", "browser_screenshot",
|
|
524
|
+
})
|
|
525
|
+
#: Tools that act on, or read, the person's own screen. None is ever approved automatically,
|
|
526
|
+
#: and a blanket "allow all tools" does not cover them (see tool_executor).
|
|
527
|
+
DESKTOP_TOOLS = frozenset({"computer_action", "computer_screenshot"})
|
|
528
|
+
_POINTER_QUIET = frozenset({"move", "scroll"})
|
|
529
|
+
_KEY_ALIASES = {"command": "cmd", "option": "alt", "opt": "alt", "control": "ctrl", "del": "delete",
|
|
530
|
+
"return": "enter", "escape": "esc"}
|
|
531
|
+
# Combinations that close, quit, delete or reboot whatever has focus.
|
|
532
|
+
_KEY_DESTRUCTIVE = (
|
|
533
|
+
{"cmd", "q"}, {"cmd", "alt", "q"}, {"cmd", "shift", "q"}, {"alt", "f4"}, {"ctrl", "alt", "delete"},
|
|
534
|
+
{"cmd", "alt", "esc"}, {"cmd", "delete"}, {"cmd", "backspace"}, {"ctrl", "shift", "delete"},
|
|
535
|
+
{"cmd", "shift", "delete"}, {"ctrl", "shift", "q"},
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _key_parts(key: Any) -> set[str]:
|
|
540
|
+
parts = [p for p in re.split(r"[\s+]+", str(key or "").lower()) if p]
|
|
541
|
+
return {_KEY_ALIASES.get(p, p) for p in parts}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _assess_desktop(name: str, params: Mapping[str, Any]) -> RiskAssessment:
|
|
545
|
+
draft = _Draft()
|
|
546
|
+
draft.systems.append("This computer's screen")
|
|
547
|
+
if name == "computer_screenshot":
|
|
548
|
+
draft.capabilities.add("screen.capture")
|
|
549
|
+
draft.raise_to(3, "Captures everything visible on screen, which may include private content")
|
|
550
|
+
draft.summary = "Capture the screen"
|
|
551
|
+
return draft.freeze()
|
|
552
|
+
action = str(params.get("action") or "").strip().lower()
|
|
553
|
+
draft.capabilities.add("desktop.control")
|
|
554
|
+
if action in _POINTER_QUIET:
|
|
555
|
+
draft.raise_to(2, "Moves the pointer or scrolls on the live desktop")
|
|
556
|
+
draft.summary = f"Desktop {action}"
|
|
557
|
+
return draft.freeze()
|
|
558
|
+
draft.raise_to(3, "Drives the mouse or keyboard of the live desktop", reversible=False)
|
|
559
|
+
draft.summary = f"Desktop {action or 'input'}"
|
|
560
|
+
if action == "type":
|
|
561
|
+
text = str(params.get("text") or "")
|
|
562
|
+
if "\n" in text or "\r" in text:
|
|
563
|
+
draft.raise_to(4, "Types a line break, which submits or runs whatever is focused", reversible=False)
|
|
564
|
+
elif action == "key":
|
|
565
|
+
parts = _key_parts(params.get("key"))
|
|
566
|
+
if any(combo <= parts for combo in _KEY_DESTRUCTIVE):
|
|
567
|
+
draft.raise_to(4, "Key combination that quits, closes or deletes", reversible=False)
|
|
568
|
+
return draft.freeze()
|
|
569
|
+
_PATH_KEYS = ("path", "file_path", "filename", "target", "notebook_path")
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def tool_paths(params: Mapping[str, Any] | None) -> list[str]:
|
|
573
|
+
"""The file paths a tool call names."""
|
|
574
|
+
paths = []
|
|
575
|
+
for key in _PATH_KEYS:
|
|
576
|
+
value = (params or {}).get(key)
|
|
577
|
+
if isinstance(value, str) and value.strip():
|
|
578
|
+
paths.append(value.strip())
|
|
579
|
+
patch = (params or {}).get("patch")
|
|
580
|
+
if isinstance(patch, str):
|
|
581
|
+
for match in re.finditer(r"^\+\+\+ (?:b/)?(\S+)", patch, re.MULTILINE):
|
|
582
|
+
if match.group(1) != "/dev/null":
|
|
583
|
+
paths.append(match.group(1))
|
|
584
|
+
return paths
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _inside(path: str, root: Optional[Path]) -> bool:
|
|
588
|
+
if root is None:
|
|
589
|
+
return True
|
|
590
|
+
try:
|
|
591
|
+
candidate = Path(path).expanduser()
|
|
592
|
+
candidate = candidate if candidate.is_absolute() else root / candidate
|
|
593
|
+
candidate.resolve().relative_to(root.resolve())
|
|
594
|
+
return True
|
|
595
|
+
except (ValueError, OSError):
|
|
596
|
+
return False
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def assess_tool(tool: str, params: Mapping[str, Any] | None = None, *,
|
|
600
|
+
root: Optional[Path | str] = None) -> RiskAssessment:
|
|
601
|
+
"""What one tool call touches, and how badly. Never raises."""
|
|
602
|
+
name = (tool or "").strip()
|
|
603
|
+
params = params or {}
|
|
604
|
+
root_path = Path(root).expanduser() if root else None
|
|
605
|
+
if name == "run_command":
|
|
606
|
+
return assess_command(params.get("command", ""))
|
|
607
|
+
draft = _Draft()
|
|
608
|
+
if name in READ_TOOLS:
|
|
609
|
+
draft.capabilities.add("filesystem.read")
|
|
610
|
+
draft.summary = "Inspect"
|
|
611
|
+
return draft.freeze()
|
|
612
|
+
if name in WRITE_TOOLS:
|
|
613
|
+
paths = tool_paths(params)
|
|
614
|
+
draft.capabilities.add("filesystem.write")
|
|
615
|
+
draft.files.extend(paths)
|
|
616
|
+
draft.raise_to(1, "Edits files in the workspace (checkpointed)")
|
|
617
|
+
outside = [p for p in paths if not _inside(p, root_path)]
|
|
618
|
+
if outside:
|
|
619
|
+
draft.raise_to(3, "Writes outside the workspace: " + ", ".join(outside))
|
|
620
|
+
secrets = [p for p in paths if _SECRET_PATH.search(" " + p + " ")]
|
|
621
|
+
if secrets:
|
|
622
|
+
draft.capabilities.add("secret.read")
|
|
623
|
+
draft.raise_to(4, "Writes a credentials or secrets file")
|
|
624
|
+
draft.summary = "Edit files"
|
|
625
|
+
return draft.freeze()
|
|
626
|
+
if name in DESKTOP_TOOLS:
|
|
627
|
+
return _assess_desktop(name, params)
|
|
628
|
+
if name in NETWORK_READ_TOOLS:
|
|
629
|
+
draft.capabilities.add("network.read")
|
|
630
|
+
url = params.get("url")
|
|
631
|
+
if isinstance(url, str):
|
|
632
|
+
draft.hosts.extend(_hosts(url))
|
|
633
|
+
draft.raise_to(2, "Fetches from the network")
|
|
634
|
+
draft.summary = "Network request"
|
|
635
|
+
return draft.freeze()
|
|
636
|
+
if name == "github":
|
|
637
|
+
action = str(params.get("action") or params.get("command") or "")
|
|
638
|
+
return assess_command(f"gh {action}") if action else _gh_read()
|
|
639
|
+
if name in {"broker_order"}:
|
|
640
|
+
draft.capabilities.add("network.write")
|
|
641
|
+
draft.raise_to(4, "Places an order with a broker", reversible=False)
|
|
642
|
+
draft.systems.append("Brokerage account")
|
|
643
|
+
draft.summary = "Broker order"
|
|
644
|
+
return draft.freeze()
|
|
645
|
+
draft.capabilities.add("process.execute")
|
|
646
|
+
draft.raise_to(1, f"Runs the {name or 'unknown'} tool")
|
|
647
|
+
draft.summary = f"Run {name}" if name else "Unknown tool"
|
|
648
|
+
return draft.freeze()
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _gh_read() -> RiskAssessment:
|
|
652
|
+
draft = _Draft(capabilities={"network.read"}, hosts=["github.com"], summary="Read GitHub")
|
|
653
|
+
draft.raise_to(2, "Reads from GitHub")
|
|
654
|
+
return draft.freeze()
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
APPROVAL_MODES = ("manual", "risk")
|
|
658
|
+
# The highest level approval_mode=risk may run without asking. L3 (acting
|
|
659
|
+
# outside the repo) and L4 (destructive) are never automatic.
|
|
660
|
+
MAX_AUTO_LEVEL = 2
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def approval_requirement(assessment: RiskAssessment, *, mode: str = "manual", auto_level: int = 1) -> str:
|
|
664
|
+
"""How an action that would otherwise need approval should get it.
|
|
665
|
+
|
|
666
|
+
``"always"`` — L4: ask, even when this session already allowed the tool or
|
|
667
|
+
the command prefix. A standing "yes" was given for something milder.
|
|
668
|
+
``"auto"`` — ``mode="risk"`` and the action is at or below
|
|
669
|
+
``auto_level`` (capped at :data:`MAX_AUTO_LEVEL`): run without asking.
|
|
670
|
+
``"ask"`` — everything else: the usual prompt.
|
|
671
|
+
"""
|
|
672
|
+
if assessment.level >= 4:
|
|
673
|
+
return "always"
|
|
674
|
+
try:
|
|
675
|
+
ceiling = max(0, min(MAX_AUTO_LEVEL, int(auto_level)))
|
|
676
|
+
except (TypeError, ValueError):
|
|
677
|
+
ceiling = 1
|
|
678
|
+
if mode == "risk" and assessment.level <= ceiling:
|
|
679
|
+
return "auto"
|
|
680
|
+
return "ask"
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def unknown_capabilities(names: Iterable[str]) -> list[str]:
|
|
684
|
+
"""Names that are not capabilities (a contract typo), in input order."""
|
|
685
|
+
return [name for name in names if name not in CAPABILITIES]
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
__all__ = [
|
|
689
|
+
"APPROVAL_MODES",
|
|
690
|
+
"CAPABILITIES",
|
|
691
|
+
"MAX_AUTO_LEVEL",
|
|
692
|
+
"approval_requirement",
|
|
693
|
+
"LEVEL_NAMES",
|
|
694
|
+
"RiskAssessment",
|
|
695
|
+
"assess_command",
|
|
696
|
+
"assess_tool",
|
|
697
|
+
"tool_paths",
|
|
698
|
+
"unknown_capabilities",
|
|
699
|
+
]
|