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,326 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
aria_bots.relay_client — 连接 Aria 中继服务器的 WebSocket 客户端
|
|
4
|
+
================================================================
|
|
5
|
+
本机运行,把中继服务器转发来的飞书消息交给 aria_feishu_bot 处理,
|
|
6
|
+
并把 LLM 回复返回给中继服务器,再由服务器推送到飞书。
|
|
7
|
+
|
|
8
|
+
启动方式:
|
|
9
|
+
python3 aria_relay_client.py # 单次连接(断线自动重连)
|
|
10
|
+
python3 aria_relay_client.py --once # 调试:收到第一条消息后退出
|
|
11
|
+
|
|
12
|
+
所需环境变量(读取 ~/.aria/.env):
|
|
13
|
+
ARIA_RELAY_URL 中继地址,配置向导默认填 Arthera 运营的中继(见 OFFICIAL_RELAY_URL)
|
|
14
|
+
ARIA_RELAY_CLIENT_ID setup_wizard 生成的 12 位 hex id
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("aria.relay_client")
|
|
28
|
+
|
|
29
|
+
# ── 加载 ~/.aria/.env ──────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
def _load_env() -> None:
|
|
32
|
+
env_file = Path.home() / ".aria" / ".env"
|
|
33
|
+
if env_file.exists():
|
|
34
|
+
for line in env_file.read_text().splitlines():
|
|
35
|
+
line = line.strip()
|
|
36
|
+
if line and not line.startswith("#") and "=" in line:
|
|
37
|
+
k, _, v = line.partition("=")
|
|
38
|
+
k = k.strip()
|
|
39
|
+
if k not in os.environ:
|
|
40
|
+
os.environ[k] = v.strip()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_load_env()
|
|
44
|
+
|
|
45
|
+
# ── This machine's relay credentials ──────────────────────────────────────────
|
|
46
|
+
#
|
|
47
|
+
# The relay records both on first registration (as hashes): the token proves a
|
|
48
|
+
# later connection is this machine, and the bind code is what a Feishu user
|
|
49
|
+
# sends to bind to it. Both stay in ~/.aria/.env. Before 2026-10-04 there were
|
|
50
|
+
# neither: the client_id was the only identity and also the bind code, so
|
|
51
|
+
# anyone who learned it could impersonate the machine or bind their own Feishu
|
|
52
|
+
# account to it.
|
|
53
|
+
|
|
54
|
+
_BIND_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # nothing to misread: no 0/O, 1/I
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def new_token() -> str:
|
|
58
|
+
import secrets
|
|
59
|
+
return secrets.token_urlsafe(32)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def new_bind_code() -> str:
|
|
63
|
+
import secrets
|
|
64
|
+
return "".join(secrets.choice(_BIND_ALPHABET) for _ in range(12))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def format_bind_code(code: str) -> str:
|
|
68
|
+
"""ABCDEFGHJKLM → ARIA-BIND-ABCD-EFGH-JKLM, the form a person types."""
|
|
69
|
+
code = "".join(ch for ch in code.upper() if ch.isalnum())
|
|
70
|
+
return "ARIA-BIND-" + "-".join(code[i:i + 4] for i in range(0, len(code), 4))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def ensure_credentials(env_file: Path = Path.home() / ".aria" / ".env") -> tuple[str, str]:
|
|
74
|
+
"""This machine's (token, bind code), created and saved on first use."""
|
|
75
|
+
token = os.environ.get("ARIA_RELAY_CLIENT_TOKEN", "").strip()
|
|
76
|
+
code = os.environ.get("ARIA_RELAY_BIND_CODE", "").strip()
|
|
77
|
+
fresh = {}
|
|
78
|
+
if len(token) < 32:
|
|
79
|
+
token = fresh["ARIA_RELAY_CLIENT_TOKEN"] = new_token()
|
|
80
|
+
if len(code) < 10:
|
|
81
|
+
code = fresh["ARIA_RELAY_BIND_CODE"] = new_bind_code()
|
|
82
|
+
if fresh:
|
|
83
|
+
env_file.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
84
|
+
with env_file.open("a", encoding="utf-8") as handle:
|
|
85
|
+
for key, value in fresh.items():
|
|
86
|
+
handle.write(f"{key}={value}\n")
|
|
87
|
+
env_file.chmod(0o600)
|
|
88
|
+
os.environ.update(fresh)
|
|
89
|
+
if "ARIA_RELAY_BIND_CODE" in fresh:
|
|
90
|
+
logger.info("New relay bind code — send this to the Aria bot in Feishu: /bind %s",
|
|
91
|
+
format_bind_code(code))
|
|
92
|
+
return token, code
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# The relay Arthera runs. Offered as the setup wizard's default, never used as
|
|
96
|
+
# a silent fallback.
|
|
97
|
+
OFFICIAL_RELAY_URL = "wss://aria-code-741336310848.europe-west2.run.app/ws"
|
|
98
|
+
|
|
99
|
+
# The default used to be wss://relay.aria.ai. aria.ai belongs to an unrelated
|
|
100
|
+
# party (registered 2017); the subdomain does not resolve today, but its owner
|
|
101
|
+
# could create it at any time, and every client left on the default would then
|
|
102
|
+
# send its machine token, bind code and Feishu traffic there. Anyone who ran
|
|
103
|
+
# the wizard before 2026-10-04 has it saved in ~/.aria/.env, so it is refused
|
|
104
|
+
# by name, not merely no longer suggested.
|
|
105
|
+
_UNOWNED_RELAY_HOSTS = {"relay.aria.ai"}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def relay_url_problem(url: str) -> str:
|
|
109
|
+
""""" when the client may connect to `url`, otherwise why not."""
|
|
110
|
+
from urllib.parse import urlparse
|
|
111
|
+
|
|
112
|
+
if not url:
|
|
113
|
+
return "ARIA_RELAY_URL is not set — run the setup wizard (relay mode) to configure it"
|
|
114
|
+
parsed = urlparse(url)
|
|
115
|
+
host = (parsed.hostname or "").lower()
|
|
116
|
+
if host in _UNOWNED_RELAY_HOSTS or host.endswith(".aria.ai") or host == "aria.ai":
|
|
117
|
+
return (f"{host} is not run by Arthera; run the setup wizard again to switch to "
|
|
118
|
+
f"{OFFICIAL_RELAY_URL}")
|
|
119
|
+
if parsed.scheme == "wss" and host:
|
|
120
|
+
return ""
|
|
121
|
+
if parsed.scheme == "ws" and host in ("localhost", "127.0.0.1", "::1"):
|
|
122
|
+
return ""
|
|
123
|
+
return "ARIA_RELAY_URL must be wss:// — this machine's token is sent when it connects (ws:// only to localhost)"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def default_relay_url(current: str) -> str:
|
|
127
|
+
"""What the setup wizard offers: the saved URL, unless it is unusable."""
|
|
128
|
+
current = (current or "").strip()
|
|
129
|
+
return current if current and not relay_url_problem(current) else OFFICIAL_RELAY_URL
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
_RELAY_URL = os.environ.get("ARIA_RELAY_URL", "").strip()
|
|
133
|
+
_CLIENT_ID = os.environ.get("ARIA_RELAY_CLIENT_ID", "")
|
|
134
|
+
_RECONNECT_DELAY_MAX = 60 # seconds
|
|
135
|
+
_RECONNECT_DELAY_BASE = 3
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ── 本地 aria_feishu_bot import ───────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
def _get_feishu_bot():
|
|
141
|
+
try:
|
|
142
|
+
from aria_bots import feishu_bot
|
|
143
|
+
return feishu_bot
|
|
144
|
+
except ImportError as e:
|
|
145
|
+
logger.warning("feishu_bot not importable: %s", e)
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ── Message handler ───────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
async def _handle_message(raw_msg: dict, ws) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Server sends:
|
|
154
|
+
{"type": "message", "id": "req_xxx", "payload": <feishu_event_dict>}
|
|
155
|
+
|
|
156
|
+
We reply:
|
|
157
|
+
{"type": "response", "id": "req_xxx", "result": <any>}
|
|
158
|
+
"""
|
|
159
|
+
req_id = raw_msg.get("id", "")
|
|
160
|
+
payload = raw_msg.get("payload", {})
|
|
161
|
+
|
|
162
|
+
bot = _get_feishu_bot()
|
|
163
|
+
if bot is None:
|
|
164
|
+
result = {"error": "feishu_bot unavailable"}
|
|
165
|
+
else:
|
|
166
|
+
try:
|
|
167
|
+
# The relay routes an event only to the machine its sender bound
|
|
168
|
+
# with a code shown here, so that binding is the authorization.
|
|
169
|
+
result = await bot.dispatch_event(payload, authorized_by_binding=True)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.exception("dispatch_event error")
|
|
172
|
+
result = {"error": str(e)[:300]}
|
|
173
|
+
|
|
174
|
+
reply = json.dumps({"type": "response", "id": req_id, "result": result})
|
|
175
|
+
await ws.send(reply)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def register_frame(client_id: str, token: str, bind_code: str) -> dict:
|
|
179
|
+
frame = {"type": "register", "client_id": client_id, "token": token, "bind_code": bind_code}
|
|
180
|
+
secret = os.environ.get("ARIA_RELAY_SECRET", "").strip()
|
|
181
|
+
if secret:
|
|
182
|
+
frame["secret"] = secret # the deployment-wide gate, when the relay sets one
|
|
183
|
+
return frame
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ── Sending through the relay ─────────────────────────────────────────────────
|
|
187
|
+
#
|
|
188
|
+
# This machine has no Feishu app credentials in relay mode, so the bot hands
|
|
189
|
+
# each outgoing message to the relay, which sends it — and refuses anything
|
|
190
|
+
# that is not a reply to a message it forwarded here, or a post to a chat this
|
|
191
|
+
# user has spoken to the bot in.
|
|
192
|
+
|
|
193
|
+
_pending_sends: dict[str, asyncio.Future] = {}
|
|
194
|
+
_SEND_TIMEOUT = 20
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _relay_sender(ws):
|
|
198
|
+
async def send(request: dict) -> dict:
|
|
199
|
+
import uuid
|
|
200
|
+
send_id = f"send_{uuid.uuid4().hex[:10]}"
|
|
201
|
+
future = asyncio.get_running_loop().create_future()
|
|
202
|
+
_pending_sends[send_id] = future
|
|
203
|
+
try:
|
|
204
|
+
await ws.send(json.dumps({"type": "send", "id": send_id, **request}))
|
|
205
|
+
return await asyncio.wait_for(future, timeout=_SEND_TIMEOUT)
|
|
206
|
+
except asyncio.TimeoutError:
|
|
207
|
+
return {"code": -1, "msg": "relay did not confirm the send"}
|
|
208
|
+
finally:
|
|
209
|
+
_pending_sends.pop(send_id, None)
|
|
210
|
+
return send
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _settle_send(msg: dict) -> None:
|
|
214
|
+
future = _pending_sends.get(msg.get("id", ""))
|
|
215
|
+
if future is not None and not future.done():
|
|
216
|
+
future.set_result(msg.get("result") or {})
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ── Main loop ─────────────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
async def _connect_and_serve(once: bool = False) -> None:
|
|
222
|
+
try:
|
|
223
|
+
import websockets # type: ignore
|
|
224
|
+
except ImportError:
|
|
225
|
+
logger.error("websockets package not installed — run: pip install websockets")
|
|
226
|
+
sys.exit(1)
|
|
227
|
+
|
|
228
|
+
problem = relay_url_problem(_RELAY_URL)
|
|
229
|
+
if problem:
|
|
230
|
+
logger.error("Not connecting to the relay: %s", problem)
|
|
231
|
+
sys.exit(1)
|
|
232
|
+
|
|
233
|
+
if not _CLIENT_ID:
|
|
234
|
+
logger.error(
|
|
235
|
+
"ARIA_RELAY_CLIENT_ID is not set. "
|
|
236
|
+
"Run setup_wizard.py to generate your client ID."
|
|
237
|
+
)
|
|
238
|
+
sys.exit(1)
|
|
239
|
+
|
|
240
|
+
delay = _RECONNECT_DELAY_BASE
|
|
241
|
+
while True:
|
|
242
|
+
try:
|
|
243
|
+
logger.info("Connecting to %s (client_id=%s)", _RELAY_URL, _CLIENT_ID)
|
|
244
|
+
async with websockets.connect(
|
|
245
|
+
_RELAY_URL,
|
|
246
|
+
ping_interval=30,
|
|
247
|
+
ping_timeout=10,
|
|
248
|
+
open_timeout=15,
|
|
249
|
+
) as ws:
|
|
250
|
+
# Register with server
|
|
251
|
+
token, bind_code = ensure_credentials()
|
|
252
|
+
await ws.send(json.dumps(register_frame(_CLIENT_ID, token, bind_code)))
|
|
253
|
+
ack_raw = await asyncio.wait_for(ws.recv(), timeout=10)
|
|
254
|
+
ack = json.loads(ack_raw)
|
|
255
|
+
if not ack.get("ok"):
|
|
256
|
+
logger.error("Registration rejected: %s", ack.get("reason", "unknown"))
|
|
257
|
+
if "another installation" in str(ack.get("reason", "")):
|
|
258
|
+
logger.error("This client_id belongs to a different machine on the relay. "
|
|
259
|
+
"Run the setup wizard to create a new one.")
|
|
260
|
+
await asyncio.sleep(delay)
|
|
261
|
+
continue
|
|
262
|
+
|
|
263
|
+
logger.info("Registered. Waiting for messages…")
|
|
264
|
+
delay = _RECONNECT_DELAY_BASE # reset on success
|
|
265
|
+
bot = _get_feishu_bot()
|
|
266
|
+
if bot is not None and hasattr(bot, "set_relay_sender"):
|
|
267
|
+
bot.set_relay_sender(_relay_sender(ws))
|
|
268
|
+
|
|
269
|
+
async for raw in ws:
|
|
270
|
+
try:
|
|
271
|
+
msg = json.loads(raw)
|
|
272
|
+
except json.JSONDecodeError:
|
|
273
|
+
logger.warning("Invalid JSON from relay: %r", raw[:100])
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
if msg.get("type") == "ping":
|
|
277
|
+
await ws.send(json.dumps({"type": "pong"}))
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
if msg.get("type") == "send_result":
|
|
281
|
+
_settle_send(msg)
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
if msg.get("type") == "message":
|
|
285
|
+
asyncio.create_task(_handle_message(msg, ws))
|
|
286
|
+
|
|
287
|
+
if once:
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
except (OSError, ConnectionRefusedError) as e:
|
|
291
|
+
logger.warning("Connection failed: %s — retry in %ds", e, delay)
|
|
292
|
+
except asyncio.CancelledError:
|
|
293
|
+
logger.info("Relay client cancelled")
|
|
294
|
+
return
|
|
295
|
+
except Exception as e:
|
|
296
|
+
logger.warning("Relay error: %s — retry in %ds", e, delay)
|
|
297
|
+
|
|
298
|
+
# Disconnected: sends must not go to a closed socket while we retry.
|
|
299
|
+
bot = _get_feishu_bot()
|
|
300
|
+
if bot is not None and hasattr(bot, "set_relay_sender"):
|
|
301
|
+
bot.set_relay_sender(None)
|
|
302
|
+
await asyncio.sleep(delay)
|
|
303
|
+
delay = min(delay * 2, _RECONNECT_DELAY_MAX)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def main() -> None:
|
|
307
|
+
import argparse
|
|
308
|
+
parser = argparse.ArgumentParser(description="Aria 中继客户端")
|
|
309
|
+
parser.add_argument("--once", action="store_true", help="接收一条消息后退出(调试用)")
|
|
310
|
+
parser.add_argument("--verbose", "-v", action="store_true")
|
|
311
|
+
args = parser.parse_args()
|
|
312
|
+
|
|
313
|
+
logging.basicConfig(
|
|
314
|
+
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
315
|
+
format="%(asctime)s [aria-relay] %(message)s",
|
|
316
|
+
datefmt="%H:%M:%S",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
asyncio.run(_connect_and_serve(once=args.once))
|
|
321
|
+
except KeyboardInterrupt:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == "__main__":
|
|
326
|
+
main()
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""
|
|
2
|
+
aria_bots.telegram_bot — Telegram client and the aria-telegram-bot command handler.
|
|
3
|
+
|
|
4
|
+
Uses the Telegram Bot API directly via httpx (no heavy python-telegram-bot dep).
|
|
5
|
+
Supports long-polling updates and sending messages/documents back to users.
|
|
6
|
+
|
|
7
|
+
Commands handled:
|
|
8
|
+
/price SYMBOL — quick quote
|
|
9
|
+
/report SYMBOL — full analysis (async, returns text summary)
|
|
10
|
+
/brief — morning brief
|
|
11
|
+
/alerts — list active alerts
|
|
12
|
+
/alert SYMBOL cond v — add alert (e.g. /alert 600362 price_below 39.5)
|
|
13
|
+
/help — command list
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
from aria_bots.telegram_bot import TelegramBot
|
|
17
|
+
bot = TelegramBot(token="...", allowed_ids={123456})
|
|
18
|
+
await bot.start(command_handler) # command_handler(cmd, args, chat_id) -> str
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import logging
|
|
24
|
+
from typing import Any, Callable, Coroutine, Optional, Set
|
|
25
|
+
|
|
26
|
+
import httpx
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
_API = "https://api.telegram.org/bot{token}/{method}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TelegramBot:
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
token: str,
|
|
37
|
+
allowed_chat_ids: Optional[Set[int]] = None,
|
|
38
|
+
poll_timeout: int = 30,
|
|
39
|
+
):
|
|
40
|
+
self.token = token
|
|
41
|
+
self.allowed_chat_ids = allowed_chat_ids or set()
|
|
42
|
+
self.poll_timeout = poll_timeout
|
|
43
|
+
self._offset = 0
|
|
44
|
+
self._running = False
|
|
45
|
+
self._client: Optional[httpx.AsyncClient] = None
|
|
46
|
+
|
|
47
|
+
def is_allowed(self, chat_id: int) -> bool:
|
|
48
|
+
"""Fails closed: an empty allowlist allows nobody."""
|
|
49
|
+
return chat_id in self.allowed_chat_ids
|
|
50
|
+
|
|
51
|
+
# ── Low-level API ─────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def _url(self, method: str) -> str:
|
|
54
|
+
return _API.format(token=self.token, method=method)
|
|
55
|
+
|
|
56
|
+
async def _call(self, method: str, **kwargs: Any) -> Optional[dict]:
|
|
57
|
+
try:
|
|
58
|
+
if self._client is None:
|
|
59
|
+
self._client = httpx.AsyncClient(timeout=self.poll_timeout + 5)
|
|
60
|
+
resp = await self._client.post(self._url(method), json=kwargs)
|
|
61
|
+
data = resp.json()
|
|
62
|
+
if not data.get("ok"):
|
|
63
|
+
logger.warning("Telegram %s error: %s", method, data.get("description"))
|
|
64
|
+
return None
|
|
65
|
+
return data.get("result")
|
|
66
|
+
except Exception as exc:
|
|
67
|
+
logger.error("Telegram API call %s failed: %s", method, exc)
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
# ── Sending ───────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
async def send_message(
|
|
73
|
+
self, chat_id: int, text: str, parse_mode: str = "Markdown"
|
|
74
|
+
) -> bool:
|
|
75
|
+
# Telegram has a 4096-char message limit
|
|
76
|
+
if len(text) > 4096:
|
|
77
|
+
text = text[:4090] + "\n…"
|
|
78
|
+
result = await self._call(
|
|
79
|
+
"sendMessage",
|
|
80
|
+
chat_id=chat_id,
|
|
81
|
+
text=text,
|
|
82
|
+
parse_mode=parse_mode,
|
|
83
|
+
disable_web_page_preview=True,
|
|
84
|
+
)
|
|
85
|
+
return result is not None
|
|
86
|
+
|
|
87
|
+
async def send_long_message(self, chat_id: int, text: str) -> None:
|
|
88
|
+
"""Split and send messages that exceed Telegram's 4096-char limit."""
|
|
89
|
+
chunks = [text[i : i + 4000] for i in range(0, len(text), 4000)]
|
|
90
|
+
for chunk in chunks:
|
|
91
|
+
await self.send_message(chat_id, chunk)
|
|
92
|
+
|
|
93
|
+
async def send_typing(self, chat_id: int) -> None:
|
|
94
|
+
await self._call("sendChatAction", chat_id=chat_id, action="typing")
|
|
95
|
+
|
|
96
|
+
async def send_document(
|
|
97
|
+
self, chat_id: int, file_path: str, caption: str = ""
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""Send a file as a document attachment."""
|
|
100
|
+
try:
|
|
101
|
+
import aiofiles
|
|
102
|
+
url = self._url("sendDocument")
|
|
103
|
+
async with aiofiles.open(file_path, "rb") as f:
|
|
104
|
+
content = await f.read()
|
|
105
|
+
import os
|
|
106
|
+
filename = os.path.basename(file_path)
|
|
107
|
+
if self._client is None:
|
|
108
|
+
self._client = httpx.AsyncClient(timeout=60)
|
|
109
|
+
resp = await self._client.post(
|
|
110
|
+
url,
|
|
111
|
+
data={"chat_id": str(chat_id), "caption": caption},
|
|
112
|
+
files={"document": (filename, content)},
|
|
113
|
+
)
|
|
114
|
+
return resp.json().get("ok", False)
|
|
115
|
+
except ImportError:
|
|
116
|
+
# Fallback: just send caption as text
|
|
117
|
+
await self.send_message(chat_id, caption or "File ready (aiofiles not installed)")
|
|
118
|
+
return False
|
|
119
|
+
except Exception as exc:
|
|
120
|
+
logger.error("send_document failed: %s", exc)
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
# ── Polling loop ─────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
async def get_updates(self) -> list[dict]:
|
|
126
|
+
result = await self._call(
|
|
127
|
+
"getUpdates",
|
|
128
|
+
offset=self._offset,
|
|
129
|
+
timeout=self.poll_timeout,
|
|
130
|
+
allowed_updates=["message"],
|
|
131
|
+
)
|
|
132
|
+
return result or []
|
|
133
|
+
|
|
134
|
+
async def start(
|
|
135
|
+
self,
|
|
136
|
+
command_handler: Callable[[str, str, int], Coroutine[Any, Any, str]],
|
|
137
|
+
) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Start long-polling. For each received message, parse the command and
|
|
140
|
+
call command_handler(command, args, chat_id) → reply text.
|
|
141
|
+
command_handler should be an async coroutine.
|
|
142
|
+
"""
|
|
143
|
+
self._running = True
|
|
144
|
+
logger.info("Telegram bot polling started")
|
|
145
|
+
while self._running:
|
|
146
|
+
try:
|
|
147
|
+
updates = await self.get_updates()
|
|
148
|
+
except asyncio.CancelledError:
|
|
149
|
+
break
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
logger.error("Polling error: %s", exc)
|
|
152
|
+
await asyncio.sleep(5)
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
for update in updates:
|
|
156
|
+
self._offset = max(self._offset, update["update_id"] + 1)
|
|
157
|
+
msg = update.get("message", {})
|
|
158
|
+
text = (msg.get("text") or "").strip()
|
|
159
|
+
chat_id = msg.get("chat", {}).get("id")
|
|
160
|
+
if not text or not chat_id:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# ACL check. Fails closed: an empty TELEGRAM_ALLOWED_IDS allows
|
|
164
|
+
# nobody. It used to allow everybody — anyone who found the bot
|
|
165
|
+
# could drive the daemon's commands.
|
|
166
|
+
if not self.is_allowed(chat_id):
|
|
167
|
+
await self.send_message(
|
|
168
|
+
chat_id,
|
|
169
|
+
"⛔ 未授权。请将你的 Chat ID 添加到 `TELEGRAM_ALLOWED_IDS`。\n你的 ID: `" + str(chat_id) + "`",
|
|
170
|
+
)
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
asyncio.create_task(self._handle(text, chat_id, command_handler))
|
|
174
|
+
|
|
175
|
+
async def _handle(
|
|
176
|
+
self,
|
|
177
|
+
text: str,
|
|
178
|
+
chat_id: int,
|
|
179
|
+
command_handler: Callable[[str, str, int], Coroutine[Any, Any, str]],
|
|
180
|
+
) -> None:
|
|
181
|
+
# Parse "/command args" or plain text
|
|
182
|
+
if text.startswith("/"):
|
|
183
|
+
parts = text[1:].split(None, 1)
|
|
184
|
+
cmd = parts[0].lower().split("@")[0] # strip @botname suffix
|
|
185
|
+
args = parts[1] if len(parts) > 1 else ""
|
|
186
|
+
else:
|
|
187
|
+
cmd = "chat"
|
|
188
|
+
args = text
|
|
189
|
+
|
|
190
|
+
await self.send_typing(chat_id)
|
|
191
|
+
try:
|
|
192
|
+
reply = await command_handler(cmd, args, chat_id)
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
reply = f"⚠️ 执行出错: {exc}"
|
|
195
|
+
logger.exception("command_handler error cmd=%s", cmd)
|
|
196
|
+
|
|
197
|
+
if reply:
|
|
198
|
+
await self.send_long_message(chat_id, reply)
|
|
199
|
+
|
|
200
|
+
async def stop(self) -> None:
|
|
201
|
+
self._running = False
|
|
202
|
+
if self._client:
|
|
203
|
+
await self._client.aclose()
|
|
204
|
+
self._client = None
|
|
205
|
+
|
|
206
|
+
async def get_me(self) -> Optional[dict]:
|
|
207
|
+
return await self._call("getMe")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ── Commands (aria-telegram-bot) ─────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
HELP = (
|
|
213
|
+
"*Aria 命令列表*\n\n"
|
|
214
|
+
"`/price SYMBOL` — 实时报价\n"
|
|
215
|
+
"`/report SYMBOL` — 研报\n"
|
|
216
|
+
"`/brief` — 今日晨报\n"
|
|
217
|
+
"`/alert SYMBOL cond value` — 添加价格预警\n"
|
|
218
|
+
" 条件: price\\_above / price\\_below / pct\\_change\\_above / pct\\_change\\_below\n"
|
|
219
|
+
"`/alerts` — 查看预警列表\n"
|
|
220
|
+
"`/status` — Daemon 运行状态\n"
|
|
221
|
+
"\n直接发文字也可对话 Aria。"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
async def handle_command(cmd: str, args: str, chat_id: int) -> str:
|
|
226
|
+
"""Answer one Telegram command: market commands go to aria-runtime daemon,
|
|
227
|
+
everything else is a turn on the Go engine."""
|
|
228
|
+
from . import engine, market
|
|
229
|
+
|
|
230
|
+
args = args.strip()
|
|
231
|
+
try:
|
|
232
|
+
if cmd in ("help", "start"):
|
|
233
|
+
return HELP
|
|
234
|
+
if cmd in ("price", "p"):
|
|
235
|
+
symbol = args.split()[0].upper() if args else ""
|
|
236
|
+
if not symbol:
|
|
237
|
+
return "用法: `/price AAPL` 或 `/price 600519.SS`"
|
|
238
|
+
title, body, _ = market.price_card(await market.quote(symbol))
|
|
239
|
+
return f"*{title}*\n" + body.replace("**", "*")
|
|
240
|
+
if cmd in ("brief", "morning", "briefing"):
|
|
241
|
+
title, body = await market.brief("morning-brief")
|
|
242
|
+
return f"*{title}*\n{body}"
|
|
243
|
+
if cmd == "alert":
|
|
244
|
+
parts = args.split()
|
|
245
|
+
if len(parts) != 3:
|
|
246
|
+
return "用法: `/alert AAPL price_above 200`"
|
|
247
|
+
symbol, condition, value = parts[0].upper(), parts[1], parts[2]
|
|
248
|
+
await market.add_alert(symbol, condition, value)
|
|
249
|
+
return f"✅ 预警已添加:*{symbol}* {condition} `{value}`"
|
|
250
|
+
if cmd == "alerts":
|
|
251
|
+
table = await market.list_alerts()
|
|
252
|
+
return "暂无预警" if len(table.splitlines()) < 2 else f"```\n{table}\n```"
|
|
253
|
+
if cmd == "status":
|
|
254
|
+
return await market.status()
|
|
255
|
+
except market.MarketError as exc:
|
|
256
|
+
return f"⚠️ {exc}"
|
|
257
|
+
|
|
258
|
+
runtime = engine.find_engine()
|
|
259
|
+
if not runtime:
|
|
260
|
+
return "⚠️ 未找到 aria-runtime:请安装,或用 ARIA_RUNTIME 指向它。"
|
|
261
|
+
if cmd == "report":
|
|
262
|
+
symbol = args.split()[0].upper() if args else ""
|
|
263
|
+
if not symbol:
|
|
264
|
+
return "用法: `/report AAPL` 或 `/report 600519.SS`"
|
|
265
|
+
prompt = (f"为 {symbol} 写一份简明研报:最新行情、技术面、基本面要点、主要风险和结论。"
|
|
266
|
+
"只用工具取到的数据,注明来源与日期。")
|
|
267
|
+
return await engine.run_turn(prompt, engine=runtime, timeout=240)
|
|
268
|
+
if cmd == "chat":
|
|
269
|
+
return await engine.run_turn(args, engine=runtime) if args else HELP
|
|
270
|
+
return f"未知命令 `/{cmd}`。发送 `/help` 查看命令列表。"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def allowed_ids(raw: str) -> Set[int]:
|
|
274
|
+
return {int(x.strip()) for x in raw.split(",") if x.strip().lstrip("-").isdigit()}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def main() -> int:
|
|
278
|
+
"""aria-telegram-bot: long-poll Telegram with TELEGRAM_BOT_TOKEN, answering
|
|
279
|
+
only chats in TELEGRAM_ALLOWED_IDS."""
|
|
280
|
+
import os
|
|
281
|
+
|
|
282
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [telegram] %(levelname)s %(message)s")
|
|
283
|
+
token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
|
|
284
|
+
if not token:
|
|
285
|
+
logger.error("TELEGRAM_BOT_TOKEN is not set")
|
|
286
|
+
return 2
|
|
287
|
+
ids = allowed_ids(os.environ.get("TELEGRAM_ALLOWED_IDS", ""))
|
|
288
|
+
if not ids:
|
|
289
|
+
logger.warning("TELEGRAM_ALLOWED_IDS is empty: every chat will be refused")
|
|
290
|
+
bot = TelegramBot(token=token, allowed_chat_ids=ids)
|
|
291
|
+
try:
|
|
292
|
+
asyncio.run(bot.start(handle_command))
|
|
293
|
+
except KeyboardInterrupt:
|
|
294
|
+
pass
|
|
295
|
+
return 0
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
if __name__ == "__main__":
|
|
299
|
+
raise SystemExit(main())
|