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,329 @@
|
|
|
1
|
+
"""Tool execution layer for Aria Code runtimes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import re
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Awaitable, Callable, Dict, Mapping, Optional
|
|
10
|
+
|
|
11
|
+
from .approval import ApprovalDecision, apply_approval_decision
|
|
12
|
+
from .events import RuntimeTrace, ToolCallRecord
|
|
13
|
+
|
|
14
|
+
ToolHandler = Callable[[dict], dict]
|
|
15
|
+
RemoteExecutor = Callable[[str, dict], Awaitable[dict]]
|
|
16
|
+
Hook = Callable[[str, str, dict, Optional[dict]], None]
|
|
17
|
+
ExecutionContextProvider = Callable[[], Mapping[str, Any]]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ToolExecutor:
|
|
21
|
+
"""Execute local/remote tools with hooks, policy injection, and trace records."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
local_tools: Mapping[str, tuple],
|
|
26
|
+
*,
|
|
27
|
+
remote_executor: RemoteExecutor | None = None,
|
|
28
|
+
hook: Hook | None = None,
|
|
29
|
+
trace: RuntimeTrace | None = None,
|
|
30
|
+
config: Dict[str, Any] | None = None,
|
|
31
|
+
execution_context: ExecutionContextProvider | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
self.local_tools = local_tools
|
|
34
|
+
self.remote_executor = remote_executor
|
|
35
|
+
self.hook = hook
|
|
36
|
+
self.trace = trace or RuntimeTrace()
|
|
37
|
+
self.config = config or {}
|
|
38
|
+
self.execution_context = execution_context
|
|
39
|
+
|
|
40
|
+
def execute_local(
|
|
41
|
+
self, tool_name: str, params: dict, *, approval: ApprovalDecision | None = None
|
|
42
|
+
) -> dict:
|
|
43
|
+
"""Execute a local tool synchronously."""
|
|
44
|
+
if "_agent_allowed_tools" in self.config and tool_name not in self.config["_agent_allowed_tools"]:
|
|
45
|
+
return {"success": False, "error": "Tool is outside this agent role’s allowed capabilities."}
|
|
46
|
+
if self.config.get("_conversation_only"):
|
|
47
|
+
return {"success": False, "error": "This is a standalone conversational request. Reply directly without tools or file inspection."}
|
|
48
|
+
if tool_name not in self.local_tools:
|
|
49
|
+
return {"success": False, "error": f"Unknown local tool: {tool_name}"}
|
|
50
|
+
if approval is not None and not approval.approved:
|
|
51
|
+
reason = approval.reason or "Tool approval denied."
|
|
52
|
+
self.trace.emit("tool_denied", {"tool": tool_name, "reason": reason})
|
|
53
|
+
return {"success": False, "error": reason}
|
|
54
|
+
handler = self.local_tools[tool_name][0]
|
|
55
|
+
params = self._prepare_params(
|
|
56
|
+
tool_name,
|
|
57
|
+
params,
|
|
58
|
+
include_execution_context=True,
|
|
59
|
+
bind_research_context=True,
|
|
60
|
+
approval=approval,
|
|
61
|
+
)
|
|
62
|
+
context_error = params.pop("_execution_context_error", None)
|
|
63
|
+
if context_error:
|
|
64
|
+
result = {"success": False, "error": context_error}
|
|
65
|
+
self.trace.emit("tool_denied", {"tool": tool_name, "reason": context_error})
|
|
66
|
+
return result
|
|
67
|
+
return self._call_with_trace(tool_name, params, lambda: handler(params))
|
|
68
|
+
|
|
69
|
+
async def execute(self, tool_name: str, params: dict) -> dict:
|
|
70
|
+
"""Execute a tool asynchronously, using remote executor when needed."""
|
|
71
|
+
if "_agent_allowed_tools" in self.config and tool_name not in self.config["_agent_allowed_tools"]:
|
|
72
|
+
return {"success": False, "error": "Tool is outside this agent role’s allowed capabilities."}
|
|
73
|
+
if self.config.get("_conversation_only"):
|
|
74
|
+
return {"success": False, "error": "This is a standalone conversational request. Reply directly without tools or file inspection."}
|
|
75
|
+
if tool_name in self.local_tools:
|
|
76
|
+
# Scheduler operations must remain on the active event loop. A worker
|
|
77
|
+
# thread cannot create a durable background coroutine on this loop.
|
|
78
|
+
if tool_name in {"spawn_task", "task_cancel", "task_status", "task_result", "task_list", "list_agents"}:
|
|
79
|
+
return self.execute_local(tool_name, params)
|
|
80
|
+
loop = asyncio.get_event_loop()
|
|
81
|
+
return await loop.run_in_executor(None, self.execute_local, tool_name, params)
|
|
82
|
+
if self.remote_executor is None:
|
|
83
|
+
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
|
84
|
+
params = self._prepare_params(tool_name, params, bind_research_context=True)
|
|
85
|
+
start = time.time()
|
|
86
|
+
self._run_hook("pre_tool", tool_name, params)
|
|
87
|
+
self.trace.emit("tool_call", {"tool": tool_name, "params": params})
|
|
88
|
+
try:
|
|
89
|
+
result = await self.remote_executor(tool_name, params)
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
result = {"success": False, "error": str(exc)}
|
|
92
|
+
observable = {key: value for key, value in result.items() if key != "_vision_image"}
|
|
93
|
+
self._run_hook("post_tool", tool_name, params, observable)
|
|
94
|
+
end = time.time()
|
|
95
|
+
self.trace.add_tool_call(ToolCallRecord(
|
|
96
|
+
tool=tool_name,
|
|
97
|
+
params=params,
|
|
98
|
+
result=observable,
|
|
99
|
+
elapsed_ms=(end - start) * 1000,
|
|
100
|
+
started_at=start,
|
|
101
|
+
ended_at=end,
|
|
102
|
+
))
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
def _call_with_trace(self, tool_name: str, params: dict, fn: Callable[[], dict]) -> dict:
|
|
106
|
+
start = time.time()
|
|
107
|
+
self._run_hook("pre_tool", tool_name, params)
|
|
108
|
+
self.trace.emit("tool_call", {"tool": tool_name, "params": params})
|
|
109
|
+
try:
|
|
110
|
+
result = fn()
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
result = {"success": False, "error": str(exc)}
|
|
113
|
+
observable = {key: value for key, value in result.items() if key != "_vision_image"}
|
|
114
|
+
self._run_hook("post_tool", tool_name, params, observable)
|
|
115
|
+
end = time.time()
|
|
116
|
+
self.trace.add_tool_call(ToolCallRecord(
|
|
117
|
+
tool=tool_name,
|
|
118
|
+
params=params,
|
|
119
|
+
result=observable,
|
|
120
|
+
elapsed_ms=(end - start) * 1000,
|
|
121
|
+
started_at=start,
|
|
122
|
+
ended_at=end,
|
|
123
|
+
))
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
def _prepare_params(
|
|
127
|
+
self,
|
|
128
|
+
tool_name: str,
|
|
129
|
+
params: dict,
|
|
130
|
+
*,
|
|
131
|
+
include_execution_context: bool = False,
|
|
132
|
+
bind_research_context: bool = False,
|
|
133
|
+
approval: ApprovalDecision | None = None,
|
|
134
|
+
) -> dict:
|
|
135
|
+
prepared = dict(params or {})
|
|
136
|
+
# A model cannot grant itself directories or replace the host workspace.
|
|
137
|
+
for key in tuple(prepared):
|
|
138
|
+
if key.startswith("_"):
|
|
139
|
+
prepared.pop(key)
|
|
140
|
+
if tool_name == "analyze_financial_statements":
|
|
141
|
+
prepared["network_enabled"] = bool(self.config.get("network_enabled", True))
|
|
142
|
+
if tool_name == "run_command":
|
|
143
|
+
# Tool arguments come from the model. Only the host configuration and
|
|
144
|
+
# a typed approval decision may set execution controls.
|
|
145
|
+
for key in (
|
|
146
|
+
"policy", "permission_mode", "network_enabled",
|
|
147
|
+
"user_approved", "_upgrade_policy", "sandbox", "os_sandbox",
|
|
148
|
+
):
|
|
149
|
+
prepared.pop(key, None)
|
|
150
|
+
workspace_tools = {"read_file", "write_file", "edit_file", "multi_edit", "list_files",
|
|
151
|
+
"search_code", "glob", "analyze_file", "notebook_read", "notebook_edit",
|
|
152
|
+
"apply_patch", "run_command", "github", "spawn_task", "list_task_context"}
|
|
153
|
+
context = {
|
|
154
|
+
"_workspace": self.config.get("_session_workspace_root") or self.config.get("workspace_root") or str(Path.cwd()),
|
|
155
|
+
"_allowed_read_roots": [*(self.config.get("read_roots") or ()), *(self.config.get("_session_read_roots") or ())],
|
|
156
|
+
"_allowed_write_roots": [*(self.config.get("write_roots") or ()), *(self.config.get("_session_write_roots") or ())],
|
|
157
|
+
"_permission_mode": self.config.get("permission_mode", "workspace-write"),
|
|
158
|
+
} if tool_name in workspace_tools else {}
|
|
159
|
+
if self.execution_context is not None and (
|
|
160
|
+
include_execution_context or bind_research_context
|
|
161
|
+
):
|
|
162
|
+
try:
|
|
163
|
+
context.update(dict(self.execution_context() or {}))
|
|
164
|
+
if bind_research_context:
|
|
165
|
+
self._bind_research_run(tool_name, prepared, context)
|
|
166
|
+
except Exception as exc:
|
|
167
|
+
if include_execution_context:
|
|
168
|
+
prepared["_execution_context_error"] = (
|
|
169
|
+
f"Execution context unavailable: {exc}"
|
|
170
|
+
)
|
|
171
|
+
if include_execution_context:
|
|
172
|
+
for key, value in context.items():
|
|
173
|
+
if key.startswith("_") and value is not None:
|
|
174
|
+
prepared[key] = value
|
|
175
|
+
self._bind_workspace(tool_name, prepared, context)
|
|
176
|
+
self._check_file_permission(tool_name, prepared)
|
|
177
|
+
if tool_name in {"write_file", "edit_file", "multi_edit", "notebook_edit"}:
|
|
178
|
+
if approval is not None and approval.approved:
|
|
179
|
+
prepared["_skip_confirm"] = True
|
|
180
|
+
if tool_name in {"spawn_task", "list_task_context"}:
|
|
181
|
+
prepared["_task_context_enabled"] = bool(self.config.get("task_context_enabled", True))
|
|
182
|
+
prepared["_session_id"] = str(context.get("_session_id") or self.config.get("last_session_id") or "")
|
|
183
|
+
if tool_name == "spawn_task":
|
|
184
|
+
prepared["_parent_permission_mode"] = self.config.get("permission_mode", "workspace-write")
|
|
185
|
+
if tool_name == "warehouse_task":
|
|
186
|
+
# Tool arguments cannot select another conversation or permission mode.
|
|
187
|
+
session = context.get("_session_id") or self.config.get("last_session_id")
|
|
188
|
+
prepared["_warehouse_scope"] = str(session or "")
|
|
189
|
+
prepared["_warehouse_read_only"] = self.config.get("permission_mode") in {"read-only", "readonly", "read_only", "plan"}
|
|
190
|
+
if tool_name == "run_command":
|
|
191
|
+
prepared["policy"] = self.config.get("command_policy", "safe")
|
|
192
|
+
prepared["permission_mode"] = self.config.get("permission_mode", "workspace-write")
|
|
193
|
+
prepared["network_enabled"] = bool(self.config.get("network_enabled", True))
|
|
194
|
+
prepared["sandbox"] = bool(self.config.get("command_sandbox", False))
|
|
195
|
+
prepared["os_sandbox"] = self.config.get("os_sandbox", "auto")
|
|
196
|
+
if approval is not None and approval.approved:
|
|
197
|
+
apply_approval_decision(prepared, approval)
|
|
198
|
+
return prepared
|
|
199
|
+
|
|
200
|
+
def _check_file_permission(self, tool_name: str, prepared: dict) -> None:
|
|
201
|
+
if prepared.get("_execution_context_error"):
|
|
202
|
+
return
|
|
203
|
+
from aria_code.workspace.files import WorkspaceSecurity
|
|
204
|
+
write_tools = {"write_file", "edit_file", "multi_edit", "apply_patch", "notebook_edit"}
|
|
205
|
+
read_tools = {"read_file", "list_files", "search_code", "glob", "analyze_file", "notebook_read"}
|
|
206
|
+
canonical = tool_name.rsplit("__", 1)[-1]
|
|
207
|
+
mode = str(self.config.get("permission_mode", "workspace-write"))
|
|
208
|
+
if canonical in write_tools and mode in {"read-only", "readonly", "read_only", "plan"}:
|
|
209
|
+
prepared["_execution_context_error"] = "Writes are blocked in read-only mode."
|
|
210
|
+
return
|
|
211
|
+
if canonical not in write_tools | read_tools or mode == "full-access":
|
|
212
|
+
return
|
|
213
|
+
security = WorkspaceSecurity.from_tool_params(prepared)
|
|
214
|
+
try:
|
|
215
|
+
security.require_safe(
|
|
216
|
+
prepared.get("root" if canonical == "glob" else "path") or ".",
|
|
217
|
+
write=canonical in write_tools,
|
|
218
|
+
)
|
|
219
|
+
except PermissionError as exc:
|
|
220
|
+
prepared["_execution_context_error"] = (
|
|
221
|
+
f"{exc}. Add --add-dir for writes or --read-dir for reads."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def _bind_research_run(
|
|
226
|
+
tool_name: str,
|
|
227
|
+
prepared: dict,
|
|
228
|
+
context: Mapping[str, Any],
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Correlate an institutional research run with its upstream Aria run."""
|
|
231
|
+
canonical_name = str(tool_name).rsplit("__", 1)[-1]
|
|
232
|
+
if canonical_name != "research_run_create":
|
|
233
|
+
return
|
|
234
|
+
control_run_id = context.get("_run_id")
|
|
235
|
+
trace_id = context.get("_trace_id") or control_run_id
|
|
236
|
+
if control_run_id:
|
|
237
|
+
prepared.setdefault("control_run_id", str(control_run_id))
|
|
238
|
+
if trace_id:
|
|
239
|
+
prepared.setdefault("trace_id", str(trace_id))
|
|
240
|
+
|
|
241
|
+
@staticmethod
|
|
242
|
+
def _bind_workspace(tool_name: str, prepared: dict, context: Mapping[str, Any]) -> None:
|
|
243
|
+
workspace_value = context.get("_workspace")
|
|
244
|
+
if not workspace_value:
|
|
245
|
+
return
|
|
246
|
+
workspace = Path(str(workspace_value)).expanduser().resolve()
|
|
247
|
+
restricted = bool(context.get("_workspace_restricted", False))
|
|
248
|
+
# A task worktree stands in for the repository it was made from. The
|
|
249
|
+
# model still sees that repository's paths — in the project context,
|
|
250
|
+
# in earlier turns — so a path into it is taken to mean the same file
|
|
251
|
+
# in the worktree, and the user's own copy is never written.
|
|
252
|
+
origins = _origins(context.get("_workspace_origin"), workspace)
|
|
253
|
+
path_tools = {
|
|
254
|
+
"read_file",
|
|
255
|
+
"write_file",
|
|
256
|
+
"edit_file",
|
|
257
|
+
"multi_edit",
|
|
258
|
+
"list_files",
|
|
259
|
+
"search_code",
|
|
260
|
+
"glob",
|
|
261
|
+
"analyze_file",
|
|
262
|
+
"notebook_read",
|
|
263
|
+
"notebook_edit",
|
|
264
|
+
}
|
|
265
|
+
if tool_name in path_tools:
|
|
266
|
+
path_key = "root" if tool_name == "glob" else "path"
|
|
267
|
+
raw_path = str(prepared.get(path_key) or ".")
|
|
268
|
+
target = Path(raw_path).expanduser()
|
|
269
|
+
if not target.is_absolute():
|
|
270
|
+
target = workspace / target
|
|
271
|
+
target = _into_workspace(target.resolve(), origins, workspace)
|
|
272
|
+
if restricted and not target.is_relative_to(workspace):
|
|
273
|
+
prepared["_execution_context_error"] = (
|
|
274
|
+
f"Tool path is outside the isolated workspace: {target}"
|
|
275
|
+
)
|
|
276
|
+
return
|
|
277
|
+
prepared[path_key] = str(target)
|
|
278
|
+
if tool_name in {"run_command", "github"}:
|
|
279
|
+
raw_cwd = str(prepared.get("cwd") or workspace)
|
|
280
|
+
cwd = Path(raw_cwd).expanduser()
|
|
281
|
+
if not cwd.is_absolute():
|
|
282
|
+
cwd = workspace / cwd
|
|
283
|
+
cwd = _into_workspace(cwd.resolve(), origins, workspace)
|
|
284
|
+
if origins and prepared.get("command"):
|
|
285
|
+
prepared["command"] = _rebase_command(str(prepared["command"]), origins, workspace)
|
|
286
|
+
if restricted and not cwd.is_relative_to(workspace):
|
|
287
|
+
prepared["_execution_context_error"] = (
|
|
288
|
+
f"Command cwd is outside the isolated workspace: {cwd}"
|
|
289
|
+
)
|
|
290
|
+
return
|
|
291
|
+
prepared["cwd"] = str(cwd)
|
|
292
|
+
|
|
293
|
+
def _run_hook(self, hook_type: str, tool_name: str, params: dict, result: dict | None = None) -> None:
|
|
294
|
+
if self.hook is None:
|
|
295
|
+
return
|
|
296
|
+
try:
|
|
297
|
+
self.hook(hook_type, tool_name, params, result)
|
|
298
|
+
except Exception:
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _origins(value: Any, workspace: Path) -> tuple[Path, ...]:
|
|
303
|
+
"""The origin repository as written and as resolved, unless it is the workspace."""
|
|
304
|
+
if not value:
|
|
305
|
+
return ()
|
|
306
|
+
raw = Path(str(value)).expanduser()
|
|
307
|
+
found = []
|
|
308
|
+
for candidate in (raw, raw.resolve()):
|
|
309
|
+
if candidate != workspace and candidate not in found:
|
|
310
|
+
found.append(candidate)
|
|
311
|
+
return tuple(found)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _into_workspace(target: Path, origins: tuple[Path, ...], workspace: Path) -> Path:
|
|
315
|
+
if target.is_relative_to(workspace):
|
|
316
|
+
return target
|
|
317
|
+
for origin in origins:
|
|
318
|
+
if target.is_relative_to(origin):
|
|
319
|
+
return workspace / target.relative_to(origin)
|
|
320
|
+
return target
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _rebase_command(command: str, origins: tuple[Path, ...], workspace: Path) -> str:
|
|
324
|
+
"""Point absolute paths into the origin at the worktree: ``cd /repo && …``."""
|
|
325
|
+
for origin in sorted(origins, key=lambda path: len(str(path)), reverse=True):
|
|
326
|
+
pattern = re.escape(str(origin)) + r"(?=[/\s'\"`;:)|&]|$)"
|
|
327
|
+
command = re.sub(pattern, lambda _match: str(workspace), command)
|
|
328
|
+
return command
|
|
329
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Persistent per-tool execution policy: allowlist, denylist, ask-always.
|
|
2
|
+
|
|
3
|
+
Stores policy in ~/.arthera/tool_policy.json.
|
|
4
|
+
Checked in _confirm_tool_execution_decision() before any user prompt.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from runtime.tool_policy import check_tool_policy, add_to_policy
|
|
8
|
+
|
|
9
|
+
verdict = check_tool_policy("write_file") # "allow" | "deny" | "ask" | "default"
|
|
10
|
+
add_to_policy("read_file", "allow") # permanently auto-approve
|
|
11
|
+
add_to_policy("run_command", "deny") # permanently block
|
|
12
|
+
add_to_policy("edit_file", "ask") # always prompt
|
|
13
|
+
remove_from_policy("read_file") # remove from all lists
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Literal
|
|
21
|
+
from aria_code.packages.aria_core.paths import aria_home
|
|
22
|
+
|
|
23
|
+
PolicyVerdict = Literal["allow", "deny", "ask", "default"]
|
|
24
|
+
|
|
25
|
+
_DEFAULT_POLICY: dict = {
|
|
26
|
+
"allowed": [], # auto-approve without prompt
|
|
27
|
+
"denied": [], # always block, never execute
|
|
28
|
+
"ask_always": [], # always prompt even for non-CONFIRM_TOOLS
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _policy_file() -> Path:
|
|
33
|
+
return aria_home() / "tool_policy.json"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_tool_policy() -> dict:
|
|
37
|
+
"""Load policy from disk; returns defaults if missing or corrupt."""
|
|
38
|
+
f = _policy_file()
|
|
39
|
+
if f.exists():
|
|
40
|
+
try:
|
|
41
|
+
raw = json.loads(f.read_text(encoding="utf-8"))
|
|
42
|
+
return {
|
|
43
|
+
"allowed": list(raw.get("allowed", [])),
|
|
44
|
+
"denied": list(raw.get("denied", [])),
|
|
45
|
+
"ask_always": list(raw.get("ask_always", [])),
|
|
46
|
+
}
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
return {k: list(v) for k, v in _DEFAULT_POLICY.items()}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def save_tool_policy(policy: dict) -> None:
|
|
53
|
+
f = _policy_file()
|
|
54
|
+
f.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
f.write_text(json.dumps(policy, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_tool_policy(tool_name: str) -> PolicyVerdict:
|
|
59
|
+
"""Return the persistent verdict for *tool_name*.
|
|
60
|
+
|
|
61
|
+
"allow" → auto-approve, skip confirmation prompt
|
|
62
|
+
"deny" → block, never execute
|
|
63
|
+
"ask" → always prompt (overrides session auto-allow)
|
|
64
|
+
"default" → no override, fall through to normal flow
|
|
65
|
+
"""
|
|
66
|
+
policy = load_tool_policy()
|
|
67
|
+
if tool_name in policy.get("denied", []):
|
|
68
|
+
return "deny"
|
|
69
|
+
if tool_name in policy.get("allowed", []):
|
|
70
|
+
return "allow"
|
|
71
|
+
if tool_name in policy.get("ask_always", []):
|
|
72
|
+
return "ask"
|
|
73
|
+
return "default"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def add_to_policy(tool_name: str, verdict: Literal["allow", "deny", "ask"]) -> None:
|
|
77
|
+
"""Add *tool_name* to the given policy list, removing it from any other list first."""
|
|
78
|
+
policy = load_tool_policy()
|
|
79
|
+
for key in ("allowed", "denied", "ask_always"):
|
|
80
|
+
policy.setdefault(key, [])
|
|
81
|
+
if tool_name in policy[key]:
|
|
82
|
+
policy[key].remove(tool_name)
|
|
83
|
+
if verdict == "allow":
|
|
84
|
+
policy["allowed"].append(tool_name)
|
|
85
|
+
elif verdict == "deny":
|
|
86
|
+
policy["denied"].append(tool_name)
|
|
87
|
+
elif verdict == "ask":
|
|
88
|
+
policy["ask_always"].append(tool_name)
|
|
89
|
+
save_tool_policy(policy)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def remove_from_policy(tool_name: str) -> bool:
|
|
93
|
+
"""Remove *tool_name* from all lists. Returns True if it was present."""
|
|
94
|
+
policy = load_tool_policy()
|
|
95
|
+
changed = False
|
|
96
|
+
for key in ("allowed", "denied", "ask_always"):
|
|
97
|
+
if tool_name in policy.get(key, []):
|
|
98
|
+
policy[key].remove(tool_name)
|
|
99
|
+
changed = True
|
|
100
|
+
if changed:
|
|
101
|
+
save_tool_policy(policy)
|
|
102
|
+
return changed
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def policy_summary() -> dict:
|
|
106
|
+
"""Return current policy as a human-readable dict."""
|
|
107
|
+
return load_tool_policy()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Transaction points: the whole session state before each turn.
|
|
2
|
+
|
|
3
|
+
``/rewind`` used to restore files, and ``/rewind both`` then dropped the last
|
|
4
|
+
exchange, so rewinding three turns of edits left the conversation one turn
|
|
5
|
+
back and every approval granted since still in force. A transaction point
|
|
6
|
+
records, before each turn, everything that turn could change:
|
|
7
|
+
|
|
8
|
+
* the conversation, in full;
|
|
9
|
+
* the task list the model was keeping;
|
|
10
|
+
* the approvals in force (allow-all, per-tool, per-command-prefix);
|
|
11
|
+
* the newest file checkpoint, so every edit after it can be undone;
|
|
12
|
+
* whether the code at that point had passed its checks;
|
|
13
|
+
* the open task worktree and its unapplied diff.
|
|
14
|
+
|
|
15
|
+
Rewinding to a point undoes every file change recorded after it, then puts
|
|
16
|
+
the rest back as it was. Points live in ``<root>/<session_id>.jsonl``, the
|
|
17
|
+
newest ``keep_per_session`` kept, each storing only the messages its turn's
|
|
18
|
+
predecessor did not have.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import time
|
|
26
|
+
import uuid
|
|
27
|
+
from dataclasses import asdict, dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any, Optional
|
|
30
|
+
|
|
31
|
+
TRANSACTION_SCHEMA = "aria.transaction_point.v1"
|
|
32
|
+
DEFAULT_KEEP_PER_SESSION = 50
|
|
33
|
+
DEFAULT_MAX_SNAPSHOT_BYTES = 2 * 1024 * 1024
|
|
34
|
+
|
|
35
|
+
# What the code was known to be at a point: checks ran green, ran red, files
|
|
36
|
+
# changed with nothing to check them, or nothing has changed yet.
|
|
37
|
+
PASSED, FAILED, UNVERIFIED, UNCHANGED = "passed", "failed", "unverified", ""
|
|
38
|
+
# Red, but only where it was red before the change (runtime/baseline.py).
|
|
39
|
+
PREEXISTING = "preexisting"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class TransactionPoint:
|
|
44
|
+
point_id: str
|
|
45
|
+
session_id: str
|
|
46
|
+
turn: int
|
|
47
|
+
prompt: str # the message the turn after this point answered
|
|
48
|
+
created_at: float
|
|
49
|
+
messages: list = field(default_factory=list)
|
|
50
|
+
todos: list = field(default_factory=list)
|
|
51
|
+
approvals: dict = field(default_factory=dict)
|
|
52
|
+
checkpoint_sequence: int = 0
|
|
53
|
+
verdict: str = UNCHANGED
|
|
54
|
+
task: Optional[dict] = None # {task_id, repository, path, patch}
|
|
55
|
+
truncated: bool = False
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict:
|
|
58
|
+
return {"schema": TRANSACTION_SCHEMA, **asdict(self)}
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_dict(cls, data: dict) -> "TransactionPoint":
|
|
62
|
+
fields = cls.__dataclass_fields__
|
|
63
|
+
return cls(**{key: value for key, value in data.items() if key in fields})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def verdict_from_acceptance(acceptance: Any, previous: str) -> str:
|
|
67
|
+
"""The code's state after a turn, from the acceptance summary it carried.
|
|
68
|
+
|
|
69
|
+
A turn that changed nothing leaves the previous verdict standing.
|
|
70
|
+
"""
|
|
71
|
+
if not isinstance(acceptance, dict) or not acceptance.get("paths"):
|
|
72
|
+
return previous
|
|
73
|
+
verified = acceptance.get("verified")
|
|
74
|
+
if verified is True:
|
|
75
|
+
return PASSED
|
|
76
|
+
if verified is False:
|
|
77
|
+
return PREEXISTING if acceptance.get("regressions") is False else FAILED
|
|
78
|
+
return UNVERIFIED
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TransactionStore:
|
|
82
|
+
"""Points per session, appended one line per turn.
|
|
83
|
+
|
|
84
|
+
Consecutive points usually share the conversation up to the newer one's
|
|
85
|
+
last few messages, so a point whose conversation extends the previous
|
|
86
|
+
one's stores only the messages added since (``delta``). A compaction or a
|
|
87
|
+
rewind breaks the chain and the next point is stored in full. The file is
|
|
88
|
+
rewritten only to drop old points, once it holds twice as many as kept.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, root: Path | str, *, keep_per_session: int = DEFAULT_KEEP_PER_SESSION,
|
|
92
|
+
max_snapshot_bytes: int = DEFAULT_MAX_SNAPSHOT_BYTES) -> None:
|
|
93
|
+
self.root = Path(root).expanduser()
|
|
94
|
+
self.keep_per_session = max(1, int(keep_per_session))
|
|
95
|
+
self.max_snapshot_bytes = max(64 * 1024, int(max_snapshot_bytes))
|
|
96
|
+
|
|
97
|
+
def _path(self, session_id: str) -> Path:
|
|
98
|
+
safe = "".join(ch for ch in str(session_id) if ch.isalnum() or ch in "-_") or "default"
|
|
99
|
+
return self.root / f"{safe}.jsonl"
|
|
100
|
+
|
|
101
|
+
def _lines(self, session_id: str) -> list[dict]:
|
|
102
|
+
try:
|
|
103
|
+
raw = self._path(session_id).read_text(encoding="utf-8").splitlines()
|
|
104
|
+
except OSError:
|
|
105
|
+
return []
|
|
106
|
+
records = []
|
|
107
|
+
for line in raw:
|
|
108
|
+
try:
|
|
109
|
+
data = json.loads(line)
|
|
110
|
+
except ValueError:
|
|
111
|
+
continue
|
|
112
|
+
if isinstance(data, dict) and data.get("schema") == TRANSACTION_SCHEMA:
|
|
113
|
+
records.append(data)
|
|
114
|
+
return records
|
|
115
|
+
|
|
116
|
+
def _read(self, session_id: str) -> list[TransactionPoint]:
|
|
117
|
+
"""Oldest first, each with its whole conversation."""
|
|
118
|
+
points: list[TransactionPoint] = []
|
|
119
|
+
for data in self._lines(session_id):
|
|
120
|
+
delta = data.pop("delta", None)
|
|
121
|
+
try:
|
|
122
|
+
point = TransactionPoint.from_dict(data)
|
|
123
|
+
except TypeError:
|
|
124
|
+
continue
|
|
125
|
+
if delta is not None:
|
|
126
|
+
if not points or points[-1].point_id != delta.get("base"):
|
|
127
|
+
continue # its base was dropped: unrecoverable, skip it
|
|
128
|
+
prefix = points[-1].messages[: int(delta.get("start") or 0)]
|
|
129
|
+
point.messages = prefix + list(delta.get("messages") or [])
|
|
130
|
+
point.truncated = point.truncated or points[-1].truncated
|
|
131
|
+
points.append(point)
|
|
132
|
+
return points
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _encode(point: TransactionPoint, previous: Optional[TransactionPoint]) -> dict:
|
|
136
|
+
data = point.to_dict()
|
|
137
|
+
if previous is not None:
|
|
138
|
+
start = len(previous.messages)
|
|
139
|
+
if start and point.messages[:start] == previous.messages:
|
|
140
|
+
data.pop("messages")
|
|
141
|
+
data["delta"] = {"base": previous.point_id, "start": start,
|
|
142
|
+
"messages": point.messages[start:]}
|
|
143
|
+
return data
|
|
144
|
+
|
|
145
|
+
def _rewrite(self, session_id: str, points: list[TransactionPoint]) -> None:
|
|
146
|
+
path = self._path(session_id)
|
|
147
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
148
|
+
temp = path.with_suffix(".tmp")
|
|
149
|
+
previous = None
|
|
150
|
+
with temp.open("w", encoding="utf-8") as handle:
|
|
151
|
+
for point in points[-self.keep_per_session:]:
|
|
152
|
+
handle.write(json.dumps(self._encode(point, previous), ensure_ascii=False, default=str) + "\n")
|
|
153
|
+
previous = point
|
|
154
|
+
os.replace(temp, path)
|
|
155
|
+
|
|
156
|
+
def _fit(self, point: TransactionPoint) -> TransactionPoint:
|
|
157
|
+
"""Drop the oldest messages until the snapshot is under the byte cap."""
|
|
158
|
+
messages = list(point.messages)
|
|
159
|
+
while messages and len(json.dumps(messages, ensure_ascii=False, default=str)) > self.max_snapshot_bytes:
|
|
160
|
+
messages.pop(0)
|
|
161
|
+
point.truncated = True
|
|
162
|
+
point.messages = messages
|
|
163
|
+
return point
|
|
164
|
+
|
|
165
|
+
def record(self, point: TransactionPoint) -> TransactionPoint:
|
|
166
|
+
# Round-trip through JSON so the stored and the compared forms agree.
|
|
167
|
+
point.messages = json.loads(json.dumps(point.messages, ensure_ascii=False, default=str))
|
|
168
|
+
point = self._fit(point)
|
|
169
|
+
points = self._read(point.session_id)
|
|
170
|
+
if len(points) + 1 > 2 * self.keep_per_session:
|
|
171
|
+
self._rewrite(point.session_id, points + [point])
|
|
172
|
+
return point
|
|
173
|
+
path = self._path(point.session_id)
|
|
174
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
175
|
+
line = json.dumps(self._encode(point, points[-1] if points else None), ensure_ascii=False, default=str)
|
|
176
|
+
with path.open("a", encoding="utf-8") as handle:
|
|
177
|
+
handle.write(line + "\n")
|
|
178
|
+
return point
|
|
179
|
+
|
|
180
|
+
def list(self, session_id: str) -> list[TransactionPoint]:
|
|
181
|
+
"""Newest first, at most ``keep_per_session``."""
|
|
182
|
+
return list(reversed(self._read(session_id)[-self.keep_per_session:]))
|
|
183
|
+
|
|
184
|
+
def find(self, session_id: str, ref: str) -> Optional[TransactionPoint]:
|
|
185
|
+
"""By id (or its prefix), or by count: ``"1"`` is the point before the last turn."""
|
|
186
|
+
points = self.list(session_id)
|
|
187
|
+
ref = str(ref or "1").strip()
|
|
188
|
+
if ref.isdigit() and len(ref) <= 3:
|
|
189
|
+
index = int(ref) - 1
|
|
190
|
+
return points[index] if 0 <= index < len(points) else None
|
|
191
|
+
matches = [point for point in points if point.point_id.startswith(ref)]
|
|
192
|
+
return matches[0] if len(matches) == 1 else None
|
|
193
|
+
|
|
194
|
+
def latest_green(self, session_id: str) -> Optional[TransactionPoint]:
|
|
195
|
+
return next((point for point in self.list(session_id) if point.verdict == PASSED), None)
|
|
196
|
+
|
|
197
|
+
def drop_from(self, point: TransactionPoint) -> None:
|
|
198
|
+
"""Forget ``point`` and every later one: after a rewind they are not history."""
|
|
199
|
+
kept = []
|
|
200
|
+
for candidate in self._read(point.session_id):
|
|
201
|
+
if candidate.point_id == point.point_id:
|
|
202
|
+
break
|
|
203
|
+
kept.append(candidate)
|
|
204
|
+
self._rewrite(point.session_id, kept)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def new_point(*, session_id: str, turn: int, prompt: str, **state: Any) -> TransactionPoint:
|
|
208
|
+
return TransactionPoint(point_id=uuid.uuid4().hex[:10], session_id=session_id, turn=turn,
|
|
209
|
+
prompt=" ".join(str(prompt or "").split())[:120],
|
|
210
|
+
created_at=time.time(), **state)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def default_transaction_store() -> TransactionStore:
|
|
214
|
+
from ..packages.aria_core.paths import aria_home
|
|
215
|
+
|
|
216
|
+
return TransactionStore(aria_home() / "transactions")
|