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
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
"""Persistent terminal application service. The host owns the TTY.
|
|
2
|
+
|
|
3
|
+
Bidirectional UTF-8 JSONL, protocol 1: submit/cancel a turn, answer a menu or
|
|
4
|
+
input request, resume a session, and shut down. Inference, commands, task
|
|
5
|
+
isolation, risk assessment, tool approvals and persistence stay in the existing
|
|
6
|
+
ArtheraTerminal. Only its presentation/input adapters are replaced here.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import asyncio
|
|
12
|
+
import builtins
|
|
13
|
+
import getpass
|
|
14
|
+
import io
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import queue
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import threading
|
|
21
|
+
import uuid
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
MAX_MESSAGE = 1_048_576
|
|
25
|
+
MAX_INPUT = 65_536
|
|
26
|
+
PROTOCOL = 1
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def session_id(value: str) -> str:
|
|
30
|
+
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,127}", value):
|
|
31
|
+
raise ValueError("Invalid session ID")
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Output(io.TextIOBase):
|
|
36
|
+
"""Capture legacy command/Rich output; never write it on the protocol pipe."""
|
|
37
|
+
def __init__(self, server):
|
|
38
|
+
self.server = server
|
|
39
|
+
|
|
40
|
+
def write(self, text):
|
|
41
|
+
if text:
|
|
42
|
+
# Bound an individual event even when a legacy command prints a file.
|
|
43
|
+
for offset in range(0, len(text), 16_384):
|
|
44
|
+
self.server.emit("output.delta", text=text[offset:offset + 16_384])
|
|
45
|
+
return len(text)
|
|
46
|
+
|
|
47
|
+
def flush(self):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def isatty(self):
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Server:
|
|
55
|
+
def __init__(self, incoming, outgoing, options):
|
|
56
|
+
self.incoming, self.outgoing, self.options = incoming, outgoing, options
|
|
57
|
+
self.lock = threading.Lock()
|
|
58
|
+
self.requests = queue.Queue(maxsize=32)
|
|
59
|
+
self.dialog_lock = threading.Lock()
|
|
60
|
+
self.dialog = None
|
|
61
|
+
self.turn_id = None
|
|
62
|
+
self.task = None
|
|
63
|
+
self.loop = None
|
|
64
|
+
self.stopping = threading.Event()
|
|
65
|
+
self.terminal = None
|
|
66
|
+
self.cli = None
|
|
67
|
+
self.background = []
|
|
68
|
+
self.tool_lock = threading.Lock()
|
|
69
|
+
self.active_tools = 0
|
|
70
|
+
self.cancel_requested = False
|
|
71
|
+
|
|
72
|
+
def emit(self, kind, **fields):
|
|
73
|
+
record = {"type": kind, "protocol": PROTOCOL, "turn_id": self.turn_id, **fields}
|
|
74
|
+
encoded = json.dumps(record, ensure_ascii=False, default=str) + "\n"
|
|
75
|
+
if len(encoded.encode("utf-8")) > MAX_MESSAGE:
|
|
76
|
+
raise ValueError("Application event exceeds 1 MiB")
|
|
77
|
+
with self.lock:
|
|
78
|
+
self.outgoing.write(encoded)
|
|
79
|
+
self.outgoing.flush()
|
|
80
|
+
|
|
81
|
+
def ask(self, kind, **fields):
|
|
82
|
+
ident = uuid.uuid4().hex
|
|
83
|
+
replies = queue.Queue(maxsize=1)
|
|
84
|
+
with self.dialog_lock:
|
|
85
|
+
self.dialog = (ident, replies)
|
|
86
|
+
self.emit(kind, request_id=ident, **fields)
|
|
87
|
+
try:
|
|
88
|
+
value = replies.get()
|
|
89
|
+
if value is None:
|
|
90
|
+
raise KeyboardInterrupt
|
|
91
|
+
return value
|
|
92
|
+
finally:
|
|
93
|
+
with self.dialog_lock:
|
|
94
|
+
self.dialog = None
|
|
95
|
+
self.emit("dialog.closed", request_id=ident)
|
|
96
|
+
|
|
97
|
+
def input(self, prompt="", *, secret=False):
|
|
98
|
+
value = self.ask("input.requested", prompt=str(prompt), secret=secret)
|
|
99
|
+
text = value.get("text")
|
|
100
|
+
if not isinstance(text, str) or len(text.encode("utf-8")) > MAX_INPUT:
|
|
101
|
+
raise ValueError("Invalid input response")
|
|
102
|
+
return text
|
|
103
|
+
|
|
104
|
+
def select(self, options, selected=0, title="", **kwargs):
|
|
105
|
+
# The existing approval function supplies its actual options. Returning
|
|
106
|
+
# an index to it preserves deny lists, L4 ask-every-time, hooks, symbol
|
|
107
|
+
# previews and the exact scope of session grants.
|
|
108
|
+
choices = [[str(label), str(help_text)] for label, help_text in options]
|
|
109
|
+
extra = {}
|
|
110
|
+
picker = bool(kwargs.get("picker"))
|
|
111
|
+
if picker:
|
|
112
|
+
# A long list to search (models): the frontend draws a picker, not an approval.
|
|
113
|
+
extra["picker"] = True
|
|
114
|
+
groups = kwargs.get("groups")
|
|
115
|
+
if isinstance(groups, list) and len(groups) == len(choices):
|
|
116
|
+
extra["groups"] = [str(group) for group in groups]
|
|
117
|
+
if not picker and getattr(self, "approval_risk", None):
|
|
118
|
+
extra["risk"] = self.approval_risk
|
|
119
|
+
answer = self.ask("approval.requested", title=str(title or "Approval / selection"),
|
|
120
|
+
choices=choices, selected=selected,
|
|
121
|
+
shortcuts=kwargs.get("shortcuts", {}),
|
|
122
|
+
context="" if picker else getattr(self, "approval_context", ""),
|
|
123
|
+
**extra)
|
|
124
|
+
chosen = answer.get("choice", -1)
|
|
125
|
+
if type(chosen) is not int or not -1 <= chosen < len(choices):
|
|
126
|
+
raise ValueError("Invalid menu response")
|
|
127
|
+
return chosen
|
|
128
|
+
|
|
129
|
+
def abort(self):
|
|
130
|
+
with self.dialog_lock:
|
|
131
|
+
if self.dialog:
|
|
132
|
+
try:
|
|
133
|
+
self.dialog[1].put_nowait(None)
|
|
134
|
+
except queue.Full:
|
|
135
|
+
pass
|
|
136
|
+
if self.loop:
|
|
137
|
+
def cancel():
|
|
138
|
+
if self.terminal and self.terminal.cancel_event:
|
|
139
|
+
self.terminal.cancel_event.set()
|
|
140
|
+
if self.task and not self.task.done() and not self.cancel_requested:
|
|
141
|
+
self.cancel_requested = True
|
|
142
|
+
self.task.cancel()
|
|
143
|
+
self.loop.call_soon_threadsafe(cancel)
|
|
144
|
+
|
|
145
|
+
def read_requests(self):
|
|
146
|
+
try:
|
|
147
|
+
while not self.stopping.is_set():
|
|
148
|
+
raw = self.incoming.readline(MAX_MESSAGE + 1)
|
|
149
|
+
if not raw:
|
|
150
|
+
break
|
|
151
|
+
if len(raw) > MAX_MESSAGE or not raw.endswith(b"\n"):
|
|
152
|
+
self.emit("protocol.error", error="Request must be a JSONL line of at most 1 MiB")
|
|
153
|
+
break
|
|
154
|
+
try:
|
|
155
|
+
value = json.loads(raw.decode("utf-8"))
|
|
156
|
+
if not isinstance(value, dict) or type(value.get("protocol")) is not int or value["protocol"] != PROTOCOL:
|
|
157
|
+
raise ValueError("Unsupported application protocol")
|
|
158
|
+
kind = value.get("type")
|
|
159
|
+
if kind == "dialog.respond":
|
|
160
|
+
with self.dialog_lock:
|
|
161
|
+
if not self.dialog or value.get("request_id") != self.dialog[0] or value.get("turn_id") != self.turn_id:
|
|
162
|
+
raise ValueError("Stale or unknown dialog")
|
|
163
|
+
self.dialog[1].put_nowait(value)
|
|
164
|
+
continue
|
|
165
|
+
if kind == "turn.cancel":
|
|
166
|
+
if value.get("turn_id") != self.turn_id or not self.turn_id:
|
|
167
|
+
raise ValueError("Stale or unknown turn")
|
|
168
|
+
self.abort()
|
|
169
|
+
continue
|
|
170
|
+
if kind == "shutdown":
|
|
171
|
+
break
|
|
172
|
+
if kind == "completion.request":
|
|
173
|
+
# Answered here, not queued behind a running turn: it is
|
|
174
|
+
# a lookup, and the list it feeds should not wait.
|
|
175
|
+
self.complete_reference(value)
|
|
176
|
+
continue
|
|
177
|
+
self.requests.put_nowait(value)
|
|
178
|
+
except (ValueError, UnicodeError, queue.Full) as error:
|
|
179
|
+
self.emit("protocol.error", error=str(error))
|
|
180
|
+
finally:
|
|
181
|
+
self.stopping.set()
|
|
182
|
+
self.abort()
|
|
183
|
+
# Wake the dispatcher even if the queue is full.
|
|
184
|
+
try:
|
|
185
|
+
self.requests.put_nowait({"type": "shutdown"})
|
|
186
|
+
except queue.Full:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def initialize(self):
|
|
190
|
+
# Resolve the selected package, not a legacy top-level installation.
|
|
191
|
+
from aria_code import aria_cli as cli
|
|
192
|
+
from rich.console import Console
|
|
193
|
+
from aria_code.apps.cli.runtime_consumer import TerminalRuntimeEventConsumer
|
|
194
|
+
from aria_code.apps.cli.exec_events import ExecEvents
|
|
195
|
+
|
|
196
|
+
self.cli = cli
|
|
197
|
+
output = Output(self)
|
|
198
|
+
# Console captures markup/tables without ANSI. Command modules get the
|
|
199
|
+
# same console through AriaContext. Model-visible reasoning is never
|
|
200
|
+
# sent as answer/output events by this consumer.
|
|
201
|
+
sys.stdout = output
|
|
202
|
+
sys.stderr = output
|
|
203
|
+
cli.console = Console(file=output, force_terminal=False, color_system=None, width=100)
|
|
204
|
+
cli.HAS_RICH = True
|
|
205
|
+
builtins.input = self.input
|
|
206
|
+
getpass.getpass = lambda prompt="Password: ", stream=None: self.input(prompt, secret=True)
|
|
207
|
+
cli.console.input = lambda prompt="", **kw: self.input(prompt, secret=bool(kw.get("password")))
|
|
208
|
+
cli._arrow_select = self.select
|
|
209
|
+
cli._render_answer_block = lambda text, *a, **kw: self.emit("answer.replace", text=str(text))
|
|
210
|
+
server = self
|
|
211
|
+
|
|
212
|
+
class Events(ExecEvents):
|
|
213
|
+
def __init__(self):
|
|
214
|
+
self.streaming = True
|
|
215
|
+
def emit(self, kind, **fields):
|
|
216
|
+
server.emit(kind, **fields)
|
|
217
|
+
|
|
218
|
+
events = Events()
|
|
219
|
+
|
|
220
|
+
class Consumer(TerminalRuntimeEventConsumer):
|
|
221
|
+
def __init__(self, **kwargs):
|
|
222
|
+
kwargs.update(has_rich=False, action_view=None, print_tool_call=None,
|
|
223
|
+
print_tool_done=None, on_response_start=None,
|
|
224
|
+
set_robot_state=None)
|
|
225
|
+
super().__init__(**kwargs)
|
|
226
|
+
def on_token(self, token):
|
|
227
|
+
self.first_token_received = True
|
|
228
|
+
self.streamed_any = True
|
|
229
|
+
self.token_count += 1
|
|
230
|
+
self.response_text += token
|
|
231
|
+
events.text_delta(token)
|
|
232
|
+
def on_thinking(self, content):
|
|
233
|
+
# No hidden reasoning in the protocol/transcript.
|
|
234
|
+
self.thinking_tokens += 1
|
|
235
|
+
self.set_phase(cli.TurnPhase.THINKING)
|
|
236
|
+
def on_tool_call(self, tool, params):
|
|
237
|
+
events.tool_started(tool, params)
|
|
238
|
+
def on_tool_result(self, tool, result):
|
|
239
|
+
events.tool_completed(tool, result)
|
|
240
|
+
def on_status(self, state, message):
|
|
241
|
+
events.status(state, message)
|
|
242
|
+
def stop_live(self, discard=False):
|
|
243
|
+
self.stop_spinner()
|
|
244
|
+
|
|
245
|
+
cli.TerminalRuntimeEventConsumer = Consumer
|
|
246
|
+
approval_base = cli.TerminalApprovalEventConsumer
|
|
247
|
+
class Approvals(approval_base):
|
|
248
|
+
async def approve(self, tool_name, tool_params, **kwargs):
|
|
249
|
+
from aria_code.apps.cli.exec_events import _param_view
|
|
250
|
+
params = _param_view(tool_params)
|
|
251
|
+
zh = str(server.terminal.config.get("ui_lang", "en")).startswith("zh")
|
|
252
|
+
context = [("工具" if zh else "Tool") + ": " + tool_name]
|
|
253
|
+
for key, en, cn in (("path", "File", "文件"), ("command", "Command", "命令"), ("directory", "Directory", "目录")):
|
|
254
|
+
if isinstance(params.get(key), str):
|
|
255
|
+
context.append((cn if zh else en) + ": " + params[key])
|
|
256
|
+
server.approval_context = "\n".join(context)
|
|
257
|
+
server.approval_risk = server.assess_for_approval(tool_name, tool_params)
|
|
258
|
+
try:
|
|
259
|
+
return await super().approve(tool_name, tool_params, **kwargs)
|
|
260
|
+
finally:
|
|
261
|
+
server.approval_context = ""
|
|
262
|
+
server.approval_risk = None
|
|
263
|
+
cli.TerminalApprovalEventConsumer = Approvals
|
|
264
|
+
config = cli.load_config()
|
|
265
|
+
config["_session_workspace_root"] = str(Path.cwd().resolve())
|
|
266
|
+
config["_session_write_roots"] = [str(Path(p).expanduser().resolve()) for p in self.options.add_dir]
|
|
267
|
+
config["_session_read_roots"] = [str(Path(p).expanduser().resolve()) for p in self.options.read_dir]
|
|
268
|
+
if self.options.resume_last and not self.options.resume:
|
|
269
|
+
self.options.resume = config.get("last_session_id")
|
|
270
|
+
if not self.options.resume:
|
|
271
|
+
raise ValueError("No last session to resume")
|
|
272
|
+
resumed = self.load_session(self.options.resume) if self.options.resume else None
|
|
273
|
+
config["last_session_id"] = self.options.resume or uuid.uuid4().hex[:8]
|
|
274
|
+
if self.options.model:
|
|
275
|
+
model = self.options.model
|
|
276
|
+
if "/" in model and not model.startswith("http"):
|
|
277
|
+
from aria_code.apps.cli.providers.chat_routing import normalize_provider_name, KNOWN_MODEL_PROVIDERS
|
|
278
|
+
provider, model = model.split("/", 1)
|
|
279
|
+
provider = normalize_provider_name(provider)
|
|
280
|
+
if provider in KNOWN_MODEL_PROVIDERS:
|
|
281
|
+
config["backend_chat"] = False
|
|
282
|
+
config.update(model=model, local_provider=provider,
|
|
283
|
+
local_mode=provider in {"ollama", "lmstudio", "vllm", "llamacpp", "jan", "custom"})
|
|
284
|
+
else:
|
|
285
|
+
key = cli.resolve_model_key(model)
|
|
286
|
+
config.update(model=cli.MODELS[key]["id"] if key in cli.MODELS else model, local_provider="ollama")
|
|
287
|
+
if self.options.url:
|
|
288
|
+
config["api_url"] = self.options.url
|
|
289
|
+
if self.options.thinking:
|
|
290
|
+
config["thinking_mode"] = "thinking"
|
|
291
|
+
if self.options.local:
|
|
292
|
+
config["local_mode"] = True
|
|
293
|
+
cli._auto_approve_session = self.options.dangerously_skip_permissions
|
|
294
|
+
cli._session_always_allow.update(t.strip() for t in self.options.allow_tools.split(",") if t.strip())
|
|
295
|
+
self.terminal = self.make_terminal(config)
|
|
296
|
+
if resumed:
|
|
297
|
+
self.terminal.conversation = resumed["messages"]
|
|
298
|
+
self.persist()
|
|
299
|
+
self.remember_session()
|
|
300
|
+
self.snapshot("session.ready")
|
|
301
|
+
|
|
302
|
+
def make_terminal(self, config):
|
|
303
|
+
terminal = self.cli.ArtheraTerminal(config)
|
|
304
|
+
# The host draws its own turn footer. Keep this presentation flag out
|
|
305
|
+
# of saved user settings and retain normal Python CLI behaviour.
|
|
306
|
+
terminal._native_frontend = True
|
|
307
|
+
# Commands that need a choice (/model) draw it in the host's own menu.
|
|
308
|
+
# This process's stdin is a pipe, so the TTY arrow picker can never run.
|
|
309
|
+
terminal._native_select = self.select
|
|
310
|
+
execute = terminal.tool_executor.execute_local
|
|
311
|
+
def tracked(*args, **kwargs):
|
|
312
|
+
with self.tool_lock:
|
|
313
|
+
self.active_tools += 1
|
|
314
|
+
try:
|
|
315
|
+
return execute(*args, **kwargs)
|
|
316
|
+
finally:
|
|
317
|
+
with self.tool_lock:
|
|
318
|
+
self.active_tools -= 1
|
|
319
|
+
terminal.tool_executor.execute_local = tracked
|
|
320
|
+
return terminal
|
|
321
|
+
|
|
322
|
+
def load_session(self, ident):
|
|
323
|
+
# Validate before the constructor creates a JSONL log for this ID.
|
|
324
|
+
ident = session_id(ident)
|
|
325
|
+
data = self.cli.SessionManager(self.cli.SESSIONS_DIR).load_session(ident)
|
|
326
|
+
if not data:
|
|
327
|
+
from aria_code.apps.cli.session_jsonl import JsonlSessionStore
|
|
328
|
+
data = JsonlSessionStore().load_session(ident)
|
|
329
|
+
if not data:
|
|
330
|
+
raise ValueError("Session not found")
|
|
331
|
+
messages = data.get("messages")
|
|
332
|
+
if not isinstance(messages, list) or any(not isinstance(m, dict) for m in messages):
|
|
333
|
+
raise ValueError("Invalid session messages")
|
|
334
|
+
return data
|
|
335
|
+
|
|
336
|
+
def end_hooks(self):
|
|
337
|
+
if self.cli._HAS_JSON_HOOKS:
|
|
338
|
+
try:
|
|
339
|
+
self.cli._fire_json_hook("SessionEnd", session_id=self.terminal.session_id,
|
|
340
|
+
hooks=self.cli._JSON_HOOKS)
|
|
341
|
+
except Exception:
|
|
342
|
+
pass
|
|
343
|
+
self.cli._run_event_hook("session_end", {"ARIA_SESSION": self.terminal.session_id})
|
|
344
|
+
|
|
345
|
+
def switch_session(self, ident=None):
|
|
346
|
+
data = self.load_session(ident) if ident is not None else None
|
|
347
|
+
self.persist()
|
|
348
|
+
self.end_hooks()
|
|
349
|
+
config = dict(self.terminal.config, last_session_id=ident or uuid.uuid4().hex[:8])
|
|
350
|
+
registry = self.terminal._mcp_registry
|
|
351
|
+
self.cli._session_always_allow.clear()
|
|
352
|
+
self.cli._session_command_prefixes.clear()
|
|
353
|
+
self.cli._auto_approve_session = self.options.dangerously_skip_permissions
|
|
354
|
+
self.cli._session_always_allow.update(t.strip() for t in self.options.allow_tools.split(",") if t.strip())
|
|
355
|
+
# A fresh terminal also resets plans, images, loaded-project context,
|
|
356
|
+
# telemetry, tool trace and pending artifacts. MCP connections survive.
|
|
357
|
+
self.terminal = self.make_terminal(config)
|
|
358
|
+
self.terminal._mcp_registry = registry
|
|
359
|
+
if data:
|
|
360
|
+
self.terminal.conversation = data["messages"]
|
|
361
|
+
self.persist()
|
|
362
|
+
self.remember_session()
|
|
363
|
+
self.snapshot("session.ready")
|
|
364
|
+
|
|
365
|
+
def remember_session(self):
|
|
366
|
+
# Save only the pointer in the user configuration, not transient
|
|
367
|
+
# project roots or launch-only model/permission overrides.
|
|
368
|
+
config = self.cli.load_config()
|
|
369
|
+
config["last_session_id"] = self.terminal.session_id
|
|
370
|
+
self.cli.save_config(config)
|
|
371
|
+
|
|
372
|
+
def complete_reference(self, value):
|
|
373
|
+
"""Answer a completion request with candidates for the `@file` being typed."""
|
|
374
|
+
text = value.get("text")
|
|
375
|
+
if not isinstance(text, str) or len(text.encode("utf-8")) > MAX_INPUT:
|
|
376
|
+
raise ValueError("Completion text must be at most 65536 UTF-8 bytes")
|
|
377
|
+
from aria_code.apps.cli.file_references import reference_candidates
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
candidates = reference_candidates(text, Path.cwd())
|
|
381
|
+
except Exception:
|
|
382
|
+
candidates = []
|
|
383
|
+
self.emit("completion.result", session_id=value.get("session_id", ""), text=text, candidates=candidates)
|
|
384
|
+
|
|
385
|
+
def assess_for_approval(self, tool_name, params):
|
|
386
|
+
"""What the action reaches, for the approval card: level, files, hosts, why.
|
|
387
|
+
|
|
388
|
+
The same assessment the terminal's text card is built from, sent as data
|
|
389
|
+
so the frontend can draw it. None when it cannot be assessed; the dialog
|
|
390
|
+
then shows only the tool and its target.
|
|
391
|
+
"""
|
|
392
|
+
try:
|
|
393
|
+
from aria_code.safety.risk import assess_tool
|
|
394
|
+
from aria_code.ui.render.output import _file_impact
|
|
395
|
+
|
|
396
|
+
assessment = assess_tool(tool_name, dict(params or {}), root=os.getcwd())
|
|
397
|
+
card = assessment.as_dict()
|
|
398
|
+
card["impact"] = _file_impact(assessment)
|
|
399
|
+
for key in ("hosts", "files", "systems", "reasons"):
|
|
400
|
+
card[key] = [str(x)[:200] for x in card.get(key, [])[:8]]
|
|
401
|
+
return card
|
|
402
|
+
except Exception:
|
|
403
|
+
return None
|
|
404
|
+
|
|
405
|
+
def commands_info(self):
|
|
406
|
+
"""Every slash command with what it does, in the order the list shows them.
|
|
407
|
+
|
|
408
|
+
The frontend's list opens on a bare `/`, so what comes first matters: the
|
|
409
|
+
core commands and the entry points, then the rest alphabetically, the same
|
|
410
|
+
order the terminal completer has always used. Descriptions are localised.
|
|
411
|
+
"""
|
|
412
|
+
from aria_code.apps.cli.commands.catalog import CORE_SLASH_COMMANDS, ENTRY_SLASH_COMMANDS, describe
|
|
413
|
+
|
|
414
|
+
t = self.terminal
|
|
415
|
+
registry = t.commands.commands
|
|
416
|
+
lang = t.config.get("ui_lang", "en")
|
|
417
|
+
first = [n for n in (*CORE_SLASH_COMMANDS, *ENTRY_SLASH_COMMANDS) if n in registry]
|
|
418
|
+
rest = [n for n in sorted(registry) if n not in set(first)]
|
|
419
|
+
info = []
|
|
420
|
+
for name in first + rest:
|
|
421
|
+
try:
|
|
422
|
+
detail = describe(name, registry[name][1], lang)
|
|
423
|
+
except Exception:
|
|
424
|
+
detail = ""
|
|
425
|
+
info.append({"name": name, "description": str(detail or "")[:160]})
|
|
426
|
+
return info
|
|
427
|
+
|
|
428
|
+
def snapshot(self, kind):
|
|
429
|
+
from aria_code.apps.cli.native_branding import robot_rows
|
|
430
|
+
from aria_code.ui import robot
|
|
431
|
+
t = self.terminal
|
|
432
|
+
rows = robot_rows(hidden=self.options.no_banner or self.options.banner == "off")
|
|
433
|
+
# Older Rust frontends still consume the spans. New frontends draw the
|
|
434
|
+
# named design themselves, sharing the same mascot as the Go runtime.
|
|
435
|
+
# An empty drawing must continue to mean that the banner is disabled.
|
|
436
|
+
# A display snapshot is bounded independently of the model's context.
|
|
437
|
+
messages = [{"role": m.get("role", ""), "content": str(m.get("content", ""))[:4096]}
|
|
438
|
+
for m in t.conversation[-40:]]
|
|
439
|
+
self.emit(kind, session_id=t.session_id, messages=messages,
|
|
440
|
+
version=self.cli.__version__,
|
|
441
|
+
robot=rows, robot_design="cream-block-v2" if rows else "",
|
|
442
|
+
model=t.config.get("model", ""), provider=t.config.get("local_provider", ""),
|
|
443
|
+
workspace=str(Path.cwd()), commands=sorted(t.commands.commands),
|
|
444
|
+
commands_info=self.commands_info(), reference_completion=True,
|
|
445
|
+
permission=t.config.get("permission_mode", "workspace-write"),
|
|
446
|
+
network=bool(t.config.get("network_enabled", True)),
|
|
447
|
+
ui_lang=t.config.get("ui_lang", "en"),
|
|
448
|
+
ui_theme=robot._theme_cache or robot._resolve_theme(),
|
|
449
|
+
home_workspace=Path.cwd().resolve() == Path.home().resolve())
|
|
450
|
+
|
|
451
|
+
def persist(self):
|
|
452
|
+
t = self.terminal
|
|
453
|
+
if t.config.get("auto_save_sessions", True):
|
|
454
|
+
t.session_mgr.save_session(t.session_id, t.conversation)
|
|
455
|
+
|
|
456
|
+
async def turn(self, value):
|
|
457
|
+
t = self.terminal
|
|
458
|
+
t._last_turn_envelope = None
|
|
459
|
+
t._last_response = ""
|
|
460
|
+
# Set by the turn when it has already told the user, in their own
|
|
461
|
+
# language, what went wrong; cleared here so one turn's answer is
|
|
462
|
+
# never taken for the next one's.
|
|
463
|
+
t._error_presented = False
|
|
464
|
+
t.cancel_event = asyncio.Event()
|
|
465
|
+
self.emit("turn.started", prompt=value["text"])
|
|
466
|
+
outcome, error = "completed", ""
|
|
467
|
+
try:
|
|
468
|
+
prompt = value["text"]
|
|
469
|
+
if t.commands.is_command(prompt.strip()):
|
|
470
|
+
await t.commands.execute(prompt.strip())
|
|
471
|
+
elif prompt.startswith("!"):
|
|
472
|
+
# User-initiated shell commands still go through the executor's
|
|
473
|
+
# workspace/command policy, rather than bypassing it with shell=True.
|
|
474
|
+
from aria_code.runtime.approval import ApprovalDecision
|
|
475
|
+
result = await asyncio.to_thread(t.tool_executor.execute_local, "run_command",
|
|
476
|
+
{"command": prompt[1:].strip()},
|
|
477
|
+
approval=ApprovalDecision.allow(policy=t.config.get("command_policy", "safe"), user_approved=True))
|
|
478
|
+
self.emit("output.delta", text=json.dumps(result, ensure_ascii=False, default=str))
|
|
479
|
+
t.conversation.append({"role": "user", "content": f"[shell {prompt}]\n{result}"})
|
|
480
|
+
else:
|
|
481
|
+
await t.send_message(prompt, route_text=True)
|
|
482
|
+
envelope = t._last_turn_envelope
|
|
483
|
+
if envelope:
|
|
484
|
+
outcome = "cancelled" if envelope.cancelled else str(envelope.status)
|
|
485
|
+
error = str(envelope.error or "")
|
|
486
|
+
if t._last_response:
|
|
487
|
+
self.emit("answer.replace", text=t._last_response)
|
|
488
|
+
except (asyncio.CancelledError, KeyboardInterrupt):
|
|
489
|
+
outcome = "cancelled"
|
|
490
|
+
except (Exception, SystemExit) as exc:
|
|
491
|
+
outcome, error = "failed", str(exc)
|
|
492
|
+
finally:
|
|
493
|
+
# Cancelling an asyncio Future cannot stop an executor thread.
|
|
494
|
+
# Never accept a next turn while a tool from this one is still
|
|
495
|
+
# running. The native host kills the worker/process group if this
|
|
496
|
+
# boundary cannot settle within its cancellation grace period.
|
|
497
|
+
while True:
|
|
498
|
+
with self.tool_lock:
|
|
499
|
+
busy = self.active_tools
|
|
500
|
+
if not busy:
|
|
501
|
+
break
|
|
502
|
+
await asyncio.sleep(0.01)
|
|
503
|
+
t._streaming = False
|
|
504
|
+
t.cancel_event = None
|
|
505
|
+
try:
|
|
506
|
+
self.persist()
|
|
507
|
+
except Exception as exc:
|
|
508
|
+
outcome, error = "failed", f"Session save failed: {exc}"
|
|
509
|
+
# `error` stays the raw text for scripts and exec mode. `presented`
|
|
510
|
+
# tells a human-facing front end it has already been shown, so it
|
|
511
|
+
# does not print the same failure again under an error heading.
|
|
512
|
+
self.emit("turn.completed", status=outcome, error=error,
|
|
513
|
+
presented=bool(error and getattr(t, "_error_presented", False)),
|
|
514
|
+
session_id=t.session_id)
|
|
515
|
+
self.turn_id = None
|
|
516
|
+
self.snapshot("session.state")
|
|
517
|
+
|
|
518
|
+
async def update_notice(self):
|
|
519
|
+
# Only metadata, after session.ready. Respect the same preference as
|
|
520
|
+
# the old UI; never install software or wait on the check at startup.
|
|
521
|
+
if (os.environ.get("ARIA_OFFLINE", "").lower() in {"1", "true", "yes", "on"}
|
|
522
|
+
or not self.terminal.config.get("check_for_update_on_startup", True)):
|
|
523
|
+
return
|
|
524
|
+
try:
|
|
525
|
+
from aria_code.apps.cli.update_check import start_update_check, get_update_notice
|
|
526
|
+
from rich.text import Text
|
|
527
|
+
start_update_check(self.cli.__version__, lang=self.terminal.config.get("ui_lang", "en"))
|
|
528
|
+
for _ in range(100):
|
|
529
|
+
notice = get_update_notice(wait_ms=0)
|
|
530
|
+
if notice:
|
|
531
|
+
self.emit("update.available", turn_id=None, text=Text.from_markup(notice).plain)
|
|
532
|
+
await self.install_update_quietly()
|
|
533
|
+
return
|
|
534
|
+
await asyncio.sleep(0.1)
|
|
535
|
+
except Exception:
|
|
536
|
+
pass # A metadata failure must not interrupt the user's task.
|
|
537
|
+
|
|
538
|
+
async def install_update_quietly(self):
|
|
539
|
+
"""Managed macOS/Linux installs update in the background.
|
|
540
|
+
|
|
541
|
+
Windows presents its PowerShell installer command in the update notice
|
|
542
|
+
instead: replacing the running executable is blocked by Windows file
|
|
543
|
+
locks, so installing there must happen after the user closes Aria.
|
|
544
|
+
"""
|
|
545
|
+
if sys.platform == "win32":
|
|
546
|
+
return
|
|
547
|
+
from aria_code.apps.cli import auto_update, updater
|
|
548
|
+
from aria_code.apps.cli.update_check import _install_channel, _newer
|
|
549
|
+
|
|
550
|
+
loop = asyncio.get_running_loop()
|
|
551
|
+
installed = await loop.run_in_executor(
|
|
552
|
+
None,
|
|
553
|
+
lambda: auto_update.install_if_newer(
|
|
554
|
+
self.cli.__version__, _install_channel(), self.terminal.config,
|
|
555
|
+
check=lambda channel: updater.check_update(channel, prefer_native=True),
|
|
556
|
+
install=updater.install_native,
|
|
557
|
+
newer=_newer, app_root=updater._app_root(),
|
|
558
|
+
),
|
|
559
|
+
)
|
|
560
|
+
if installed:
|
|
561
|
+
zh = self.terminal.config.get("ui_lang", "en") == "zh"
|
|
562
|
+
text = (f"已更新到 {installed} · 重启 Aria 后生效" if zh
|
|
563
|
+
else f"Updated to {installed} · restart Aria to use it")
|
|
564
|
+
self.emit("update.available", turn_id=None, text=text)
|
|
565
|
+
|
|
566
|
+
async def serve(self):
|
|
567
|
+
self.loop = asyncio.get_running_loop()
|
|
568
|
+
threading.Thread(target=self.read_requests, daemon=True).start()
|
|
569
|
+
self.initialize()
|
|
570
|
+
self.background.append(asyncio.create_task(self.update_notice()))
|
|
571
|
+
# Start configured MCP connections without delaying the first frame.
|
|
572
|
+
# Keep the same registration path used by the Python REPL.
|
|
573
|
+
if self.cli._HAS_MCP:
|
|
574
|
+
async def mcp():
|
|
575
|
+
try:
|
|
576
|
+
from aria_code.mcp_client import MCPToolRegistry
|
|
577
|
+
registry = MCPToolRegistry()
|
|
578
|
+
self.terminal._mcp_registry = registry
|
|
579
|
+
results = await registry.start_all()
|
|
580
|
+
if results:
|
|
581
|
+
registry.register_into(self.cli.LOCAL_TOOLS, self.cli.LOCAL_TOOL_SCHEMAS)
|
|
582
|
+
self.cli._mcp_registry = registry
|
|
583
|
+
except Exception:
|
|
584
|
+
pass
|
|
585
|
+
self.background.append(asyncio.create_task(mcp()))
|
|
586
|
+
while not self.stopping.is_set():
|
|
587
|
+
value = await asyncio.to_thread(self.requests.get)
|
|
588
|
+
kind = value.get("type")
|
|
589
|
+
if kind == "shutdown":
|
|
590
|
+
break
|
|
591
|
+
try:
|
|
592
|
+
if self.task and not self.task.done():
|
|
593
|
+
raise ValueError("A turn is already running")
|
|
594
|
+
if kind == "turn.submit":
|
|
595
|
+
text = value.get("text")
|
|
596
|
+
ident = value.get("turn_id")
|
|
597
|
+
if not isinstance(text, str) or not text.strip() or len(text.encode("utf-8")) > MAX_INPUT:
|
|
598
|
+
raise ValueError("Turn text must be 1..65536 UTF-8 bytes")
|
|
599
|
+
if not isinstance(ident, str) or not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", ident):
|
|
600
|
+
raise ValueError("Invalid turn ID")
|
|
601
|
+
self.turn_id = ident
|
|
602
|
+
self.cancel_requested = False
|
|
603
|
+
self.task = asyncio.create_task(self.turn(value))
|
|
604
|
+
elif kind == "session.resume":
|
|
605
|
+
self.switch_session(value.get("session_id", ""))
|
|
606
|
+
elif kind == "session.new":
|
|
607
|
+
self.switch_session()
|
|
608
|
+
else:
|
|
609
|
+
raise ValueError("Unknown application request")
|
|
610
|
+
except (ValueError, OSError, TypeError) as exc:
|
|
611
|
+
self.emit("protocol.error", error=str(exc))
|
|
612
|
+
self.abort()
|
|
613
|
+
if self.task:
|
|
614
|
+
try:
|
|
615
|
+
await self.task
|
|
616
|
+
except (asyncio.CancelledError, KeyboardInterrupt):
|
|
617
|
+
pass
|
|
618
|
+
self.persist()
|
|
619
|
+
for task in self.background:
|
|
620
|
+
task.cancel()
|
|
621
|
+
if self.terminal._mcp_registry:
|
|
622
|
+
await self.terminal._mcp_registry.stop_all()
|
|
623
|
+
from aria_code.runtime.processes import stop_all
|
|
624
|
+
await asyncio.to_thread(stop_all)
|
|
625
|
+
self.end_hooks()
|
|
626
|
+
self.emit("session.closed")
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def main():
|
|
630
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
631
|
+
parser.add_argument("--model")
|
|
632
|
+
parser.add_argument("--url")
|
|
633
|
+
parser.add_argument("--resume")
|
|
634
|
+
parser.add_argument("--resume-last", action="store_true")
|
|
635
|
+
parser.add_argument("--no-banner", action="store_true")
|
|
636
|
+
parser.add_argument("--banner", choices=["full", "compact", "off"])
|
|
637
|
+
parser.add_argument("--thinking", action="store_true")
|
|
638
|
+
parser.add_argument("--local", action="store_true")
|
|
639
|
+
parser.add_argument("--add-dir", action="append", default=[])
|
|
640
|
+
parser.add_argument("--read-dir", action="append", default=[])
|
|
641
|
+
parser.add_argument("--allow-tools", default="")
|
|
642
|
+
parser.add_argument("--dangerously-skip-permissions", action="store_true")
|
|
643
|
+
options = parser.parse_args()
|
|
644
|
+
incoming, outgoing = sys.stdin.buffer, sys.stdout
|
|
645
|
+
server = Server(incoming, outgoing, options)
|
|
646
|
+
try:
|
|
647
|
+
asyncio.run(server.serve())
|
|
648
|
+
except (BrokenPipeError, KeyboardInterrupt):
|
|
649
|
+
return
|
|
650
|
+
except Exception as exc:
|
|
651
|
+
server.emit("session.failed", error=str(exc))
|
|
652
|
+
raise SystemExit(1) from exc
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
if __name__ == "__main__":
|
|
656
|
+
main()
|