aria-code 4.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- workspace/verify.py +113 -0
runtime/subagent.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Background subagent task system.
|
|
2
|
+
|
|
3
|
+
Allows the main agent to spawn independent sub-tasks that run concurrently.
|
|
4
|
+
Tasks are tracked in memory and optionally persisted to ~/.arthera/tasks/.
|
|
5
|
+
|
|
6
|
+
Tool functions exposed to the LLM:
|
|
7
|
+
spawn_task(prompt, context?) → {"task_id": "abc123", "status": "pending"}
|
|
8
|
+
task_status(task_id) → {"status": "running|done|failed", ...}
|
|
9
|
+
task_result(task_id) → {"result": "...", "success": bool}
|
|
10
|
+
task_list() → [{"task_id": ..., "status": ...}, ...]
|
|
11
|
+
task_cancel(task_id) → {"cancelled": bool}
|
|
12
|
+
|
|
13
|
+
Background execution is wired up in aria_cli.py via _subagent_runner().
|
|
14
|
+
If no runner is registered, spawn_task stores the task for manual execution.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import time
|
|
21
|
+
import uuid
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Callable, Dict, Optional
|
|
25
|
+
|
|
26
|
+
_TASKS: Dict[str, "SubagentTask"] = {}
|
|
27
|
+
_RUNNER: Optional[Callable] = None # set by aria_cli.py
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class SubagentTask:
|
|
32
|
+
task_id: str
|
|
33
|
+
prompt: str
|
|
34
|
+
context: str = ""
|
|
35
|
+
status: str = "pending" # pending | running | done | failed | cancelled
|
|
36
|
+
result: str = ""
|
|
37
|
+
error: str = ""
|
|
38
|
+
created_at: float = field(default_factory=time.time)
|
|
39
|
+
completed_at: float = 0.0
|
|
40
|
+
|
|
41
|
+
def age_str(self) -> str:
|
|
42
|
+
elapsed = time.time() - self.created_at
|
|
43
|
+
if elapsed < 60:
|
|
44
|
+
return f"{int(elapsed)}s"
|
|
45
|
+
if elapsed < 3600:
|
|
46
|
+
return f"{elapsed/60:.1f}m"
|
|
47
|
+
return f"{elapsed/3600:.1f}h"
|
|
48
|
+
|
|
49
|
+
def to_dict(self) -> dict:
|
|
50
|
+
return {
|
|
51
|
+
"task_id": self.task_id,
|
|
52
|
+
"status": self.status,
|
|
53
|
+
"prompt": self.prompt[:200] + ("…" if len(self.prompt) > 200 else ""),
|
|
54
|
+
"age": self.age_str(),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def register_runner(runner: Callable) -> None:
|
|
59
|
+
"""Register the async runner function from aria_cli.py."""
|
|
60
|
+
global _RUNNER
|
|
61
|
+
_RUNNER = runner
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ── Tool functions ─────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
def tool_spawn_task(params: dict) -> dict:
|
|
67
|
+
"""Spawn an independent background agent task."""
|
|
68
|
+
prompt = params.get("prompt", "")
|
|
69
|
+
context = params.get("context", "")
|
|
70
|
+
if not prompt:
|
|
71
|
+
return {"success": False, "error": "Missing 'prompt'"}
|
|
72
|
+
|
|
73
|
+
task_id = uuid.uuid4().hex[:8]
|
|
74
|
+
task = SubagentTask(task_id=task_id, prompt=prompt, context=context)
|
|
75
|
+
_TASKS[task_id] = task
|
|
76
|
+
|
|
77
|
+
if _RUNNER is not None:
|
|
78
|
+
try:
|
|
79
|
+
loop = asyncio.get_event_loop()
|
|
80
|
+
if loop.is_running():
|
|
81
|
+
asyncio.ensure_future(_run_background(task))
|
|
82
|
+
else:
|
|
83
|
+
loop.run_until_complete(_run_background(task))
|
|
84
|
+
except Exception as exc:
|
|
85
|
+
task.status = "failed"
|
|
86
|
+
task.error = str(exc)
|
|
87
|
+
else:
|
|
88
|
+
# No runner — task stays in "pending" for manual execution
|
|
89
|
+
task.status = "pending"
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"success": True,
|
|
93
|
+
"task_id": task_id,
|
|
94
|
+
"status": task.status,
|
|
95
|
+
"message": (
|
|
96
|
+
f"Task {task_id} spawned. Use task_status('{task_id}') to check progress."
|
|
97
|
+
if task.status == "running" else
|
|
98
|
+
f"Task {task_id} queued (no runner registered). Check /tasks."
|
|
99
|
+
),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def _run_background(task: SubagentTask) -> None:
|
|
104
|
+
task.status = "running"
|
|
105
|
+
try:
|
|
106
|
+
full_prompt = task.prompt
|
|
107
|
+
if task.context:
|
|
108
|
+
full_prompt = f"{task.context}\n\n{task.prompt}"
|
|
109
|
+
result_text = await _RUNNER(full_prompt)
|
|
110
|
+
task.result = result_text or ""
|
|
111
|
+
task.status = "done"
|
|
112
|
+
except asyncio.CancelledError:
|
|
113
|
+
task.status = "cancelled"
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
task.error = str(exc)
|
|
116
|
+
task.status = "failed"
|
|
117
|
+
finally:
|
|
118
|
+
task.completed_at = time.time()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def tool_task_status(params: dict) -> dict:
|
|
122
|
+
"""Check the status of a background task."""
|
|
123
|
+
task_id = params.get("task_id", "")
|
|
124
|
+
if not task_id:
|
|
125
|
+
return {"success": False, "error": "Missing 'task_id'"}
|
|
126
|
+
task = _TASKS.get(task_id)
|
|
127
|
+
if not task:
|
|
128
|
+
return {"success": False, "error": f"Task '{task_id}' not found"}
|
|
129
|
+
return {
|
|
130
|
+
"success": True,
|
|
131
|
+
"task_id": task_id,
|
|
132
|
+
"status": task.status,
|
|
133
|
+
"age": task.age_str(),
|
|
134
|
+
"prompt_preview": task.prompt[:100],
|
|
135
|
+
"error": task.error or None,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def tool_task_result(params: dict) -> dict:
|
|
140
|
+
"""Retrieve the result of a completed background task."""
|
|
141
|
+
task_id = params.get("task_id", "")
|
|
142
|
+
if not task_id:
|
|
143
|
+
return {"success": False, "error": "Missing 'task_id'"}
|
|
144
|
+
task = _TASKS.get(task_id)
|
|
145
|
+
if not task:
|
|
146
|
+
return {"success": False, "error": f"Task '{task_id}' not found"}
|
|
147
|
+
if task.status == "running":
|
|
148
|
+
return {"success": False, "error": "Task is still running", "status": "running"}
|
|
149
|
+
if task.status == "failed":
|
|
150
|
+
return {"success": False, "error": task.error, "status": "failed"}
|
|
151
|
+
if task.status in ("pending", "cancelled"):
|
|
152
|
+
return {"success": False, "error": f"Task status is '{task.status}'", "status": task.status}
|
|
153
|
+
return {
|
|
154
|
+
"success": True,
|
|
155
|
+
"task_id": task_id,
|
|
156
|
+
"status": task.status,
|
|
157
|
+
"result": task.result,
|
|
158
|
+
"age": task.age_str(),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def tool_task_list(params: dict) -> dict:
|
|
163
|
+
"""List all tracked background tasks."""
|
|
164
|
+
tasks = [t.to_dict() for t in _TASKS.values()]
|
|
165
|
+
if not tasks:
|
|
166
|
+
return {"success": True, "tasks": [], "message": "No active tasks."}
|
|
167
|
+
by_status: dict = {}
|
|
168
|
+
for t in tasks:
|
|
169
|
+
s = t["status"]
|
|
170
|
+
by_status.setdefault(s, []).append(t)
|
|
171
|
+
return {
|
|
172
|
+
"success": True,
|
|
173
|
+
"total": len(tasks),
|
|
174
|
+
"tasks": tasks,
|
|
175
|
+
"summary": {s: len(v) for s, v in by_status.items()},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def tool_task_cancel(params: dict) -> dict:
|
|
180
|
+
"""Cancel a pending or running background task."""
|
|
181
|
+
task_id = params.get("task_id", "")
|
|
182
|
+
if not task_id:
|
|
183
|
+
return {"success": False, "error": "Missing 'task_id'"}
|
|
184
|
+
task = _TASKS.get(task_id)
|
|
185
|
+
if not task:
|
|
186
|
+
return {"success": False, "error": f"Task '{task_id}' not found"}
|
|
187
|
+
if task.status in ("done", "failed", "cancelled"):
|
|
188
|
+
return {"success": False, "error": f"Task already in terminal state: {task.status}"}
|
|
189
|
+
task.status = "cancelled"
|
|
190
|
+
task.completed_at = time.time()
|
|
191
|
+
return {"success": True, "task_id": task_id, "cancelled": True}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ── Tool registry (added to LOCAL_TOOLS in aria_cli.py) ───────────────────────
|
|
195
|
+
|
|
196
|
+
SUBAGENT_TOOLS = {
|
|
197
|
+
"spawn_task": (tool_spawn_task, "Spawn a background sub-task; returns task_id"),
|
|
198
|
+
"task_status": (tool_task_status, "Check status of a background task by task_id"),
|
|
199
|
+
"task_result": (tool_task_result, "Retrieve result of a completed background task"),
|
|
200
|
+
"task_list": (tool_task_list, "List all active background tasks"),
|
|
201
|
+
"task_cancel": (tool_task_cancel, "Cancel a pending or running background task"),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
SUBAGENT_SCHEMAS = [
|
|
205
|
+
{
|
|
206
|
+
"name": "spawn_task",
|
|
207
|
+
"description": "Spawn an independent background agent task. Useful for parallelising slow operations: research, data fetching, long analysis. Returns a task_id you can poll with task_status.",
|
|
208
|
+
"parameters": {
|
|
209
|
+
"type": "object",
|
|
210
|
+
"properties": {
|
|
211
|
+
"prompt": {"type": "string", "description": "The task for the sub-agent to perform"},
|
|
212
|
+
"context": {"type": "string", "description": "Optional background context to inject into the sub-agent's system prompt"},
|
|
213
|
+
},
|
|
214
|
+
"required": ["prompt"],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"name": "task_status",
|
|
219
|
+
"description": "Check the status of a background task spawned with spawn_task.",
|
|
220
|
+
"parameters": {
|
|
221
|
+
"type": "object",
|
|
222
|
+
"properties": {
|
|
223
|
+
"task_id": {"type": "string", "description": "The task_id returned by spawn_task"},
|
|
224
|
+
},
|
|
225
|
+
"required": ["task_id"],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "task_result",
|
|
230
|
+
"description": "Retrieve the full result text of a completed background task.",
|
|
231
|
+
"parameters": {
|
|
232
|
+
"type": "object",
|
|
233
|
+
"properties": {
|
|
234
|
+
"task_id": {"type": "string", "description": "The task_id returned by spawn_task"},
|
|
235
|
+
},
|
|
236
|
+
"required": ["task_id"],
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"name": "task_list",
|
|
241
|
+
"description": "List all active and completed background tasks with their statuses.",
|
|
242
|
+
"parameters": {
|
|
243
|
+
"type": "object",
|
|
244
|
+
"properties": {},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"name": "task_cancel",
|
|
249
|
+
"description": "Cancel a pending or running background task.",
|
|
250
|
+
"parameters": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"properties": {
|
|
253
|
+
"task_id": {"type": "string", "description": "The task_id to cancel"},
|
|
254
|
+
},
|
|
255
|
+
"required": ["task_id"],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
]
|
runtime/tool_executor.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Tool execution layer for Aria Code runtimes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any, Awaitable, Callable, Dict, Mapping, Optional
|
|
8
|
+
|
|
9
|
+
from .events import RuntimeTrace, ToolCallRecord
|
|
10
|
+
|
|
11
|
+
ToolHandler = Callable[[dict], dict]
|
|
12
|
+
RemoteExecutor = Callable[[str, dict], Awaitable[dict]]
|
|
13
|
+
Hook = Callable[[str, str, dict, Optional[dict]], None]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolExecutor:
|
|
17
|
+
"""Execute local/remote tools with hooks, policy injection, and trace records."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
local_tools: Mapping[str, tuple],
|
|
22
|
+
*,
|
|
23
|
+
remote_executor: RemoteExecutor | None = None,
|
|
24
|
+
hook: Hook | None = None,
|
|
25
|
+
trace: RuntimeTrace | None = None,
|
|
26
|
+
config: Dict[str, Any] | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
self.local_tools = local_tools
|
|
29
|
+
self.remote_executor = remote_executor
|
|
30
|
+
self.hook = hook
|
|
31
|
+
self.trace = trace or RuntimeTrace()
|
|
32
|
+
self.config = config or {}
|
|
33
|
+
|
|
34
|
+
def execute_local(self, tool_name: str, params: dict) -> dict:
|
|
35
|
+
"""Execute a local tool synchronously."""
|
|
36
|
+
if tool_name not in self.local_tools:
|
|
37
|
+
return {"success": False, "error": f"Unknown local tool: {tool_name}"}
|
|
38
|
+
handler = self.local_tools[tool_name][0]
|
|
39
|
+
params = self._prepare_params(tool_name, params)
|
|
40
|
+
return self._call_with_trace(tool_name, params, lambda: handler(params))
|
|
41
|
+
|
|
42
|
+
async def execute(self, tool_name: str, params: dict) -> dict:
|
|
43
|
+
"""Execute a tool asynchronously, using remote executor when needed."""
|
|
44
|
+
if tool_name in self.local_tools:
|
|
45
|
+
params = self._prepare_params(tool_name, params)
|
|
46
|
+
loop = asyncio.get_event_loop()
|
|
47
|
+
return await loop.run_in_executor(None, self.execute_local, tool_name, params)
|
|
48
|
+
if self.remote_executor is None:
|
|
49
|
+
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
|
50
|
+
params = self._prepare_params(tool_name, params)
|
|
51
|
+
start = time.time()
|
|
52
|
+
self._run_hook("pre_tool", tool_name, params)
|
|
53
|
+
self.trace.emit("tool_call", {"tool": tool_name, "params": params})
|
|
54
|
+
try:
|
|
55
|
+
result = await self.remote_executor(tool_name, params)
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
result = {"success": False, "error": str(exc)}
|
|
58
|
+
self._run_hook("post_tool", tool_name, params, result)
|
|
59
|
+
end = time.time()
|
|
60
|
+
self.trace.add_tool_call(ToolCallRecord(
|
|
61
|
+
tool=tool_name,
|
|
62
|
+
params=params,
|
|
63
|
+
result=result,
|
|
64
|
+
elapsed_ms=(end - start) * 1000,
|
|
65
|
+
started_at=start,
|
|
66
|
+
ended_at=end,
|
|
67
|
+
))
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
def _call_with_trace(self, tool_name: str, params: dict, fn: Callable[[], dict]) -> dict:
|
|
71
|
+
start = time.time()
|
|
72
|
+
self._run_hook("pre_tool", tool_name, params)
|
|
73
|
+
self.trace.emit("tool_call", {"tool": tool_name, "params": params})
|
|
74
|
+
try:
|
|
75
|
+
result = fn()
|
|
76
|
+
except Exception as exc:
|
|
77
|
+
result = {"success": False, "error": str(exc)}
|
|
78
|
+
self._run_hook("post_tool", tool_name, params, result)
|
|
79
|
+
end = time.time()
|
|
80
|
+
self.trace.add_tool_call(ToolCallRecord(
|
|
81
|
+
tool=tool_name,
|
|
82
|
+
params=params,
|
|
83
|
+
result=result,
|
|
84
|
+
elapsed_ms=(end - start) * 1000,
|
|
85
|
+
started_at=start,
|
|
86
|
+
ended_at=end,
|
|
87
|
+
))
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
def _prepare_params(self, tool_name: str, params: dict) -> dict:
|
|
91
|
+
prepared = dict(params or {})
|
|
92
|
+
if tool_name == "run_command":
|
|
93
|
+
prepared.setdefault("policy", self.config.get("command_policy", "safe"))
|
|
94
|
+
prepared.setdefault("permission_mode", self.config.get("permission_mode", "workspace-write"))
|
|
95
|
+
prepared.setdefault("network_enabled", bool(self.config.get("network_enabled", True)))
|
|
96
|
+
return prepared
|
|
97
|
+
|
|
98
|
+
def _run_hook(self, hook_type: str, tool_name: str, params: dict, result: dict | None = None) -> None:
|
|
99
|
+
if self.hook is None:
|
|
100
|
+
return
|
|
101
|
+
try:
|
|
102
|
+
self.hook(hook_type, tool_name, params, result)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
runtime/tool_policy.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
|
|
22
|
+
PolicyVerdict = Literal["allow", "deny", "ask", "default"]
|
|
23
|
+
|
|
24
|
+
_DEFAULT_POLICY: dict = {
|
|
25
|
+
"allowed": [], # auto-approve without prompt
|
|
26
|
+
"denied": [], # always block, never execute
|
|
27
|
+
"ask_always": [], # always prompt even for non-CONFIRM_TOOLS
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _policy_file() -> Path:
|
|
32
|
+
return Path.home() / ".arthera" / "tool_policy.json"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_tool_policy() -> dict:
|
|
36
|
+
"""Load policy from disk; returns defaults if missing or corrupt."""
|
|
37
|
+
f = _policy_file()
|
|
38
|
+
if f.exists():
|
|
39
|
+
try:
|
|
40
|
+
raw = json.loads(f.read_text(encoding="utf-8"))
|
|
41
|
+
return {
|
|
42
|
+
"allowed": list(raw.get("allowed", [])),
|
|
43
|
+
"denied": list(raw.get("denied", [])),
|
|
44
|
+
"ask_always": list(raw.get("ask_always", [])),
|
|
45
|
+
}
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
return {k: list(v) for k, v in _DEFAULT_POLICY.items()}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def save_tool_policy(policy: dict) -> None:
|
|
52
|
+
f = _policy_file()
|
|
53
|
+
f.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
f.write_text(json.dumps(policy, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_tool_policy(tool_name: str) -> PolicyVerdict:
|
|
58
|
+
"""Return the persistent verdict for *tool_name*.
|
|
59
|
+
|
|
60
|
+
"allow" → auto-approve, skip confirmation prompt
|
|
61
|
+
"deny" → block, never execute
|
|
62
|
+
"ask" → always prompt (overrides session auto-allow)
|
|
63
|
+
"default" → no override, fall through to normal flow
|
|
64
|
+
"""
|
|
65
|
+
policy = load_tool_policy()
|
|
66
|
+
if tool_name in policy.get("denied", []):
|
|
67
|
+
return "deny"
|
|
68
|
+
if tool_name in policy.get("allowed", []):
|
|
69
|
+
return "allow"
|
|
70
|
+
if tool_name in policy.get("ask_always", []):
|
|
71
|
+
return "ask"
|
|
72
|
+
return "default"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def add_to_policy(tool_name: str, verdict: Literal["allow", "deny", "ask"]) -> None:
|
|
76
|
+
"""Add *tool_name* to the given policy list, removing it from any other list first."""
|
|
77
|
+
policy = load_tool_policy()
|
|
78
|
+
for key in ("allowed", "denied", "ask_always"):
|
|
79
|
+
policy.setdefault(key, [])
|
|
80
|
+
if tool_name in policy[key]:
|
|
81
|
+
policy[key].remove(tool_name)
|
|
82
|
+
if verdict == "allow":
|
|
83
|
+
policy["allowed"].append(tool_name)
|
|
84
|
+
elif verdict == "deny":
|
|
85
|
+
policy["denied"].append(tool_name)
|
|
86
|
+
elif verdict == "ask":
|
|
87
|
+
policy["ask_always"].append(tool_name)
|
|
88
|
+
save_tool_policy(policy)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def remove_from_policy(tool_name: str) -> bool:
|
|
92
|
+
"""Remove *tool_name* from all lists. Returns True if it was present."""
|
|
93
|
+
policy = load_tool_policy()
|
|
94
|
+
changed = False
|
|
95
|
+
for key in ("allowed", "denied", "ask_always"):
|
|
96
|
+
if tool_name in policy.get(key, []):
|
|
97
|
+
policy[key].remove(tool_name)
|
|
98
|
+
changed = True
|
|
99
|
+
if changed:
|
|
100
|
+
save_tool_policy(policy)
|
|
101
|
+
return changed
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def policy_summary() -> dict:
|
|
105
|
+
"""Return current policy as a human-readable dict."""
|
|
106
|
+
return load_tool_policy()
|
safety/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Safety and permission primitives for Aria Code."""
|
|
2
|
+
|
|
3
|
+
from .permissions import (
|
|
4
|
+
PermissionDecision,
|
|
5
|
+
PermissionMode,
|
|
6
|
+
PermissionService,
|
|
7
|
+
PolicyDecision,
|
|
8
|
+
classify_command_risk,
|
|
9
|
+
evaluate_command_policy,
|
|
10
|
+
normalize_command,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"PermissionDecision",
|
|
15
|
+
"PermissionMode",
|
|
16
|
+
"PermissionService",
|
|
17
|
+
"PolicyDecision",
|
|
18
|
+
"classify_command_risk",
|
|
19
|
+
"evaluate_command_policy",
|
|
20
|
+
"normalize_command",
|
|
21
|
+
]
|