God Agent AI commited on
Commit
e85db04
Β·
1 Parent(s): 77082f4

feat: v4.0 Real Execution Engine - TerminalEngine, FilesystemEngine, GitHubEngine, SelfRepairEngine, DeploymentEngine, AutonomousAgent

Browse files
Files changed (4) hide show
  1. agents/autonomous_agent.py +365 -0
  2. api/routes/execution.py +415 -0
  3. main.py +89 -42
  4. tools/real_executor.py +1027 -0
agents/autonomous_agent.py ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AutonomousAgent β€” Devin/Manus-style Execution-First Agent v4.0
3
+ NOT a chatbot. NOT a prompt wrapper.
4
+ A REAL autonomous coding operator that:
5
+ 1. Plans task graph (DAG)
6
+ 2. Executes via real terminal + filesystem
7
+ 3. Self-repairs on errors (up to 3 retries)
8
+ 4. Commits to GitHub
9
+ 5. Deploys to Vercel/HuggingFace
10
+ 6. Verifies deployment
11
+ 7. Returns live URLs + repo links
12
+ """
13
+
14
+ import asyncio
15
+ import json
16
+ import os
17
+ import re
18
+ import time
19
+ import uuid
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ import structlog
23
+
24
+ from .base_agent import BaseAgent
25
+ from tools.real_executor import RealToolRouter, get_tool_router
26
+
27
+ log = structlog.get_logger()
28
+
29
+ WORKSPACE = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
30
+ GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
31
+
32
+ AUTONOMOUS_SYSTEM = """You are GOD AGENT β€” an elite autonomous software engineer like Devin + Manus combined.
33
+
34
+ You EXECUTE, not just advise.
35
+ You CODE, not just describe.
36
+ You DEPLOY, not just plan.
37
+
38
+ YOUR MISSION CONTROL PROCESS:
39
+ 1. Analyze the goal deeply
40
+ 2. Create a concrete execution plan (task graph)
41
+ 3. Execute each step using real tools
42
+ 4. Self-repair on failures
43
+ 5. Verify results
44
+ 6. Report live URLs
45
+
46
+ AVAILABLE TOOLS (use tool_call JSON format):
47
+ - terminal.run: {"command": "bash command"}
48
+ - terminal.sequence: {"commands": ["cmd1", "cmd2"]}
49
+ - terminal.run_with_repair: {"command": "cmd", "related_files": ["file.py"]}
50
+ - fs.read: {"path": "filename"}
51
+ - fs.write: {"path": "filename", "content": "..."}
52
+ - fs.patch: {"path": "file", "old": "old text", "new": "new text"}
53
+ - fs.tree: {}
54
+ - fs.list: {"path": "dir"}
55
+ - fs.search: {"query": "text", "pattern": "*.py"}
56
+ - github.clone: {"url": "https://github.com/..."}
57
+ - github.create_repo: {"name": "repo-name", "description": "..."}
58
+ - github.commit_push: {"repo_path": "/path", "message": "feat: ...", "branch": "main"}
59
+ - github.create_pr: {"owner": "user", "repo": "name", "title": "...", "body": "...", "head": "branch"}
60
+ - deploy.vercel: {"dir": "/path", "name": "project"}
61
+ - deploy.hf: {"repo_path": "/path", "space_name": "user/space"}
62
+ - workspace.info: {}
63
+
64
+ RESPOND IN THIS FORMAT:
65
+ {
66
+ "thinking": "brief analysis",
67
+ "plan": ["step 1", "step 2", ...],
68
+ "tool_call": {"tool": "tool.name", "params": {...}},
69
+ "result_summary": "what was accomplished"
70
+ }
71
+
72
+ For multi-step tasks, respond with one tool_call at a time.
73
+ After each tool result, continue with the next step.
74
+ """
75
+
76
+
77
+ class AutonomousAgent(BaseAgent):
78
+ """
79
+ Execution-first autonomous agent.
80
+ Uses real tool router for actual file/terminal/github operations.
81
+ """
82
+
83
+ def __init__(self, ws_manager=None, ai_router=None):
84
+ super().__init__("AutonomousAgent", ws_manager, ai_router)
85
+ self.tool_router: Optional[RealToolRouter] = None
86
+
87
+ def _get_router(self) -> RealToolRouter:
88
+ if not self.tool_router:
89
+ self.tool_router = get_tool_router(
90
+ ws_manager=self.ws,
91
+ ai_router=self.ai_router,
92
+ )
93
+ return self.tool_router
94
+
95
+ async def run(self, task: str, context: Dict = {}, **kwargs) -> str:
96
+ session_id = kwargs.get("session_id", "")
97
+ task_id = kwargs.get("task_id", "")
98
+ max_steps = kwargs.get("max_steps", 20)
99
+
100
+ await self.emit(task_id, "autonomous_start", {
101
+ "agent": "AutonomousAgent",
102
+ "task": task[:100],
103
+ "max_steps": max_steps,
104
+ }, session_id)
105
+
106
+ router = self._get_router()
107
+ results_history = []
108
+ all_artifacts = []
109
+
110
+ # Initial planning pass
111
+ plan = await self._create_execution_plan(task, context, task_id=task_id, session_id=session_id)
112
+
113
+ await self.emit(task_id, "plan_ready", {
114
+ "agent": "AutonomousAgent",
115
+ "plan": plan,
116
+ "steps": len(plan),
117
+ }, session_id)
118
+
119
+ step_num = 0
120
+ conversation = []
121
+
122
+ # Build initial messages
123
+ conversation.append({
124
+ "role": "system",
125
+ "content": AUTONOMOUS_SYSTEM,
126
+ })
127
+ conversation.append({
128
+ "role": "user",
129
+ "content": (
130
+ f"GOAL: {task}\n\n"
131
+ f"EXECUTION PLAN:\n" + "\n".join(f"{i+1}. {s}" for i, s in enumerate(plan)) + "\n\n"
132
+ f"Context: {json.dumps(context)[:500]}\n\n"
133
+ f"Workspace: {WORKSPACE}\n\n"
134
+ f"Start execution. Respond with the first tool_call JSON."
135
+ ),
136
+ })
137
+
138
+ while step_num < max_steps:
139
+ step_num += 1
140
+
141
+ await self.emit(task_id, "step_start", {
142
+ "step": step_num,
143
+ "max_steps": max_steps,
144
+ }, session_id)
145
+
146
+ # Get next action from LLM
147
+ raw_response = await self.llm(
148
+ conversation,
149
+ task_id=task_id,
150
+ session_id=session_id,
151
+ temperature=0.1,
152
+ max_tokens=4096,
153
+ )
154
+
155
+ # Parse tool call
156
+ tool_call = self._parse_tool_call(raw_response)
157
+
158
+ if not tool_call:
159
+ # No more tool calls β€” task complete
160
+ await self.emit(task_id, "autonomous_complete", {
161
+ "steps": step_num,
162
+ "artifacts": all_artifacts,
163
+ "summary": raw_response[:500],
164
+ }, session_id)
165
+ results_history.append({"step": step_num, "result": raw_response})
166
+ break
167
+
168
+ tool = tool_call.get("tool", "")
169
+ params = tool_call.get("params", {})
170
+ thinking = tool_call.get("thinking", "")
171
+
172
+ if thinking:
173
+ await self.emit(task_id, "agent_thinking", {
174
+ "thought": thinking[:200],
175
+ "step": step_num,
176
+ }, session_id)
177
+
178
+ await self.emit(task_id, "tool_calling", {
179
+ "tool": tool,
180
+ "step": step_num,
181
+ "params_preview": str(params)[:100],
182
+ }, session_id)
183
+
184
+ # Execute the tool
185
+ tool_result = await router.route(
186
+ tool=tool,
187
+ params=params,
188
+ session_id=session_id,
189
+ task_id=task_id,
190
+ )
191
+
192
+ # Track artifacts
193
+ if tool == "deploy.vercel" and tool_result.get("url"):
194
+ all_artifacts.append({
195
+ "type": "deployment",
196
+ "platform": "vercel",
197
+ "url": tool_result["url"],
198
+ })
199
+ elif tool == "deploy.hf" and tool_result.get("url"):
200
+ all_artifacts.append({
201
+ "type": "deployment",
202
+ "platform": "huggingface",
203
+ "url": tool_result["url"],
204
+ })
205
+ elif tool == "github.create_repo" and tool_result.get("url"):
206
+ all_artifacts.append({
207
+ "type": "repository",
208
+ "url": tool_result["url"],
209
+ "name": tool_result.get("name", ""),
210
+ })
211
+ elif tool == "fs.write" and tool_result.get("success"):
212
+ all_artifacts.append({
213
+ "type": "file",
214
+ "path": tool_result.get("path", ""),
215
+ "lines": tool_result.get("lines", 0),
216
+ })
217
+
218
+ results_history.append({
219
+ "step": step_num,
220
+ "tool": tool,
221
+ "params": params,
222
+ "result": tool_result,
223
+ })
224
+
225
+ # Add result to conversation
226
+ conversation.append({
227
+ "role": "assistant",
228
+ "content": raw_response,
229
+ })
230
+ conversation.append({
231
+ "role": "user",
232
+ "content": (
233
+ f"Tool result for {tool}:\n"
234
+ f"Success: {tool_result.get('success', 'N/A')}\n"
235
+ f"Output: {json.dumps(tool_result)[:1500]}\n\n"
236
+ f"Artifacts so far: {json.dumps(all_artifacts)[:500]}\n\n"
237
+ + ("Continue with next step. Respond with next tool_call JSON, or if DONE respond with a final summary (no tool_call)." if step_num < max_steps else "Provide final summary.")
238
+ ),
239
+ })
240
+
241
+ # Keep conversation manageable
242
+ if len(conversation) > 30:
243
+ # Keep system + first user + last 20
244
+ conversation = conversation[:2] + conversation[-20:]
245
+
246
+ # Build final output
247
+ return self._build_final_output(task, results_history, all_artifacts, step_num)
248
+
249
+ async def _create_execution_plan(
250
+ self,
251
+ task: str,
252
+ context: Dict,
253
+ task_id: str = "",
254
+ session_id: str = "",
255
+ ) -> List[str]:
256
+ """Generate a concrete execution plan."""
257
+ messages = [
258
+ {
259
+ "role": "system",
260
+ "content": (
261
+ "You are an expert software architect. Create a concrete execution plan.\n"
262
+ "Return ONLY a JSON array of strings, no explanation.\n"
263
+ "Each step must be a concrete ACTION (not vague).\n"
264
+ 'Example: ["Create project directory", "Write main.py with FastAPI routes", "Install dependencies", "Run tests", "Deploy to Vercel"]\n'
265
+ "Max 10 steps."
266
+ ),
267
+ },
268
+ {
269
+ "role": "user",
270
+ "content": f"Task: {task}\nContext: {json.dumps(context)[:300]}",
271
+ },
272
+ ]
273
+
274
+ raw = await self.llm(messages, task_id=task_id, session_id=session_id, temperature=0.2, max_tokens=500)
275
+
276
+ try:
277
+ start = raw.find("[")
278
+ end = raw.rfind("]") + 1
279
+ if start >= 0 and end > start:
280
+ plan = json.loads(raw[start:end])
281
+ if isinstance(plan, list):
282
+ return [str(s) for s in plan[:10]]
283
+ except Exception:
284
+ pass
285
+
286
+ # Fallback plan
287
+ return [
288
+ "Analyze requirements",
289
+ "Set up project structure",
290
+ "Write core implementation",
291
+ "Add error handling",
292
+ "Test functionality",
293
+ "Package and deploy",
294
+ ]
295
+
296
+ def _parse_tool_call(self, raw: str) -> Optional[Dict]:
297
+ """Parse tool_call JSON from LLM response."""
298
+ # Try to find JSON block
299
+ patterns = [
300
+ r'```json\s*(\{.*?\})\s*```',
301
+ r'```\s*(\{.*?\})\s*```',
302
+ r'(\{[^{}]*"tool_call"[^{}]*\{.*?\}.*?\})',
303
+ ]
304
+
305
+ for pattern in patterns:
306
+ match = re.search(pattern, raw, re.DOTALL)
307
+ if match:
308
+ try:
309
+ data = json.loads(match.group(1))
310
+ if "tool_call" in data:
311
+ tc = data["tool_call"]
312
+ tc["thinking"] = data.get("thinking", "")
313
+ return tc
314
+ return data
315
+ except Exception:
316
+ pass
317
+
318
+ # Try direct JSON parse
319
+ try:
320
+ start = raw.find("{")
321
+ end = raw.rfind("}") + 1
322
+ if start >= 0 and end > start:
323
+ data = json.loads(raw[start:end])
324
+ if "tool_call" in data:
325
+ tc = data["tool_call"]
326
+ tc["thinking"] = data.get("thinking", "")
327
+ return tc
328
+ if "tool" in data:
329
+ return data
330
+ except Exception:
331
+ pass
332
+
333
+ return None
334
+
335
+ def _build_final_output(
336
+ self,
337
+ task: str,
338
+ results: List[Dict],
339
+ artifacts: List[Dict],
340
+ steps: int,
341
+ ) -> str:
342
+ """Build a comprehensive final output."""
343
+ lines = [f"## βœ… Task Complete: {task[:80]}\n"]
344
+ lines.append(f"**Steps executed:** {steps}")
345
+
346
+ if artifacts:
347
+ lines.append("\n### 🎯 Artifacts\n")
348
+ for a in artifacts:
349
+ if a["type"] == "deployment":
350
+ lines.append(f"- 🌐 **{a['platform'].title()} Deploy:** [{a['url']}]({a['url']})")
351
+ elif a["type"] == "repository":
352
+ lines.append(f"- πŸ“¦ **GitHub Repo:** [{a.get('name', a['url'])}]({a['url']})")
353
+ elif a["type"] == "file":
354
+ lines.append(f"- πŸ“„ **File:** `{a['path']}` ({a.get('lines', 0)} lines)")
355
+
356
+ # Show key steps
357
+ lines.append("\n### πŸ“‹ Execution Log\n")
358
+ for r in results[-8:]:
359
+ tool = r.get("tool", "thinking")
360
+ result = r.get("result", {})
361
+ success = result.get("success", True) if isinstance(result, dict) else True
362
+ icon = "βœ…" if success else "❌"
363
+ lines.append(f"{icon} Step {r['step']}: `{tool}`")
364
+
365
+ return "\n".join(lines)
api/routes/execution.py ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Real Execution API Routes β€” v4.0
3
+ Exposes the real tool router, filesystem, terminal, GitHub, deployment
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import os
9
+ import time
10
+ import uuid
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ import structlog
14
+ from fastapi import APIRouter, HTTPException, Request
15
+ from fastapi.responses import StreamingResponse
16
+ from pydantic import BaseModel
17
+
18
+ log = structlog.get_logger()
19
+
20
+ router = APIRouter()
21
+
22
+ WORKSPACE = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
23
+
24
+
25
+ # ─── Request Models ─────────────────────────────────────────────────────────
26
+
27
+ class ToolCallRequest(BaseModel):
28
+ tool: str
29
+ params: Dict[str, Any] = {}
30
+ session_id: str = ""
31
+ task_id: str = ""
32
+
33
+
34
+ class TerminalRequest(BaseModel):
35
+ command: str
36
+ session_id: str = ""
37
+ task_id: str = ""
38
+ cwd: Optional[str] = None
39
+ timeout: int = 60
40
+
41
+
42
+ class FileWriteRequest(BaseModel):
43
+ path: str
44
+ content: str
45
+ session_id: str = ""
46
+ task_id: str = ""
47
+
48
+
49
+ class FilePatchRequest(BaseModel):
50
+ path: str
51
+ old_content: str
52
+ new_content: str
53
+ session_id: str = ""
54
+ task_id: str = ""
55
+
56
+
57
+ class GitHubCloneRequest(BaseModel):
58
+ url: str
59
+ target: str = ""
60
+ session_id: str = ""
61
+ task_id: str = ""
62
+
63
+
64
+ class GitHubCreateRepoRequest(BaseModel):
65
+ name: str
66
+ description: str = ""
67
+ private: bool = False
68
+ session_id: str = ""
69
+ task_id: str = ""
70
+
71
+
72
+ class GitHubCommitRequest(BaseModel):
73
+ repo_path: str = WORKSPACE
74
+ message: str = "God Agent automated commit"
75
+ branch: str = "main"
76
+ session_id: str = ""
77
+ task_id: str = ""
78
+
79
+
80
+ class GitHubPRRequest(BaseModel):
81
+ owner: str
82
+ repo: str
83
+ title: str
84
+ body: str = ""
85
+ head: str = "main"
86
+ base: str = "main"
87
+ session_id: str = ""
88
+ task_id: str = ""
89
+
90
+
91
+ class DeployVercelRequest(BaseModel):
92
+ project_dir: str = WORKSPACE
93
+ project_name: str = "god-agent-app"
94
+ session_id: str = ""
95
+ task_id: str = ""
96
+
97
+
98
+ class DeployHFRequest(BaseModel):
99
+ repo_path: str = WORKSPACE
100
+ space_name: str = ""
101
+ session_id: str = ""
102
+ task_id: str = ""
103
+
104
+
105
+ class AutonomousTaskRequest(BaseModel):
106
+ goal: str
107
+ context: Dict[str, Any] = {}
108
+ session_id: str = ""
109
+ task_id: str = ""
110
+ max_steps: int = 20
111
+
112
+
113
+ # ─── Helper to get router ────────────────────────────────────────────────────
114
+
115
+ def _get_tool_router(request: Request):
116
+ ws = getattr(request.app.state, "ws_manager", None)
117
+ ai_router = getattr(request.app.state, "ai_router", None)
118
+ from tools.real_executor import get_tool_router
119
+ return get_tool_router(ws_manager=ws, ai_router=ai_router)
120
+
121
+
122
+ # ═══════════════════════════════════════════════════════════════════
123
+ # UNIFIED TOOL ROUTER ENDPOINT
124
+ # ═══════════════════════════════════════════════════════════════════
125
+
126
+ @router.post("/tool/call")
127
+ async def call_tool(body: ToolCallRequest, request: Request):
128
+ """
129
+ Universal tool call endpoint.
130
+ Supports all tools: terminal, filesystem, github, deploy.
131
+ """
132
+ router_inst = _get_tool_router(request)
133
+ result = await router_inst.route(
134
+ tool=body.tool,
135
+ params=body.params,
136
+ session_id=body.session_id,
137
+ task_id=body.task_id,
138
+ )
139
+ return result
140
+
141
+
142
+ # ═══════════════════════════════════════════════════════════════════
143
+ # TERMINAL ENDPOINTS
144
+ # ═══════════════════════════════════════════════════════════════════
145
+
146
+ @router.post("/terminal/execute")
147
+ async def terminal_execute(body: TerminalRequest, request: Request):
148
+ """Execute a shell command in the workspace."""
149
+ r = _get_tool_router(request)
150
+ result = await r.terminal.execute(
151
+ command=body.command,
152
+ session_id=body.session_id,
153
+ task_id=body.task_id,
154
+ cwd=body.cwd,
155
+ timeout=body.timeout,
156
+ )
157
+ return result
158
+
159
+
160
+ @router.get("/terminal/workspace")
161
+ async def get_workspace(request: Request):
162
+ """Get workspace information and file tree."""
163
+ r = _get_tool_router(request)
164
+ tree = await r.filesystem.tree()
165
+ info = await r.terminal.execute("df -h /tmp | tail -1 && free -h | grep Mem")
166
+ return {
167
+ "workspace": WORKSPACE,
168
+ "files": tree.get("files", []),
169
+ "file_count": tree.get("count", 0),
170
+ "system_info": info.get("output", ""),
171
+ }
172
+
173
+
174
+ # ═══════════════════════════════════════════════════════════════════
175
+ # FILESYSTEM ENDPOINTS
176
+ # ═══════════════════════════════════════════════════════════════════
177
+
178
+ @router.get("/fs/tree")
179
+ async def fs_tree(request: Request, path: str = "", depth: int = 4):
180
+ r = _get_tool_router(request)
181
+ return await r.filesystem.tree(path, depth)
182
+
183
+
184
+ @router.get("/fs/list")
185
+ async def fs_list(request: Request, path: str = ""):
186
+ r = _get_tool_router(request)
187
+ return await r.filesystem.list_dir(path)
188
+
189
+
190
+ @router.get("/fs/read")
191
+ async def fs_read(request: Request, path: str):
192
+ r = _get_tool_router(request)
193
+ return await r.filesystem.read_file(path)
194
+
195
+
196
+ @router.post("/fs/write")
197
+ async def fs_write(body: FileWriteRequest, request: Request):
198
+ r = _get_tool_router(request)
199
+ return await r.filesystem.write_file(
200
+ body.path, body.content,
201
+ session_id=body.session_id, task_id=body.task_id
202
+ )
203
+
204
+
205
+ @router.post("/fs/patch")
206
+ async def fs_patch(body: FilePatchRequest, request: Request):
207
+ r = _get_tool_router(request)
208
+ return await r.filesystem.patch_file(
209
+ body.path, body.old_content, body.new_content,
210
+ session_id=body.session_id, task_id=body.task_id
211
+ )
212
+
213
+
214
+ @router.delete("/fs/delete")
215
+ async def fs_delete(request: Request, path: str):
216
+ r = _get_tool_router(request)
217
+ return await r.filesystem.delete_file(path)
218
+
219
+
220
+ @router.get("/fs/search")
221
+ async def fs_search(request: Request, query: str, path: str = "", pattern: str = "*"):
222
+ r = _get_tool_router(request)
223
+ return await r.filesystem.search_files(query, path, pattern)
224
+
225
+
226
+ # ═══════════════════════════════════════════════════════════════════
227
+ # GITHUB ENDPOINTS
228
+ # ═══════════════════════════════════════════════════════════════════
229
+
230
+ @router.post("/github/clone")
231
+ async def github_clone(body: GitHubCloneRequest, request: Request):
232
+ r = _get_tool_router(request)
233
+ return await r.github.clone_repo(
234
+ body.url, body.target,
235
+ session_id=body.session_id, task_id=body.task_id
236
+ )
237
+
238
+
239
+ @router.post("/github/create-repo")
240
+ async def github_create_repo(body: GitHubCreateRepoRequest, request: Request):
241
+ r = _get_tool_router(request)
242
+ return await r.github.create_repo(
243
+ body.name, body.description, body.private,
244
+ session_id=body.session_id, task_id=body.task_id
245
+ )
246
+
247
+
248
+ @router.post("/github/commit-push")
249
+ async def github_commit_push(body: GitHubCommitRequest, request: Request):
250
+ r = _get_tool_router(request)
251
+ return await r.github.commit_and_push(
252
+ body.repo_path, body.message, body.branch,
253
+ session_id=body.session_id, task_id=body.task_id
254
+ )
255
+
256
+
257
+ @router.post("/github/create-pr")
258
+ async def github_create_pr(body: GitHubPRRequest, request: Request):
259
+ r = _get_tool_router(request)
260
+ return await r.github.create_pr(
261
+ body.owner, body.repo, body.title, body.body,
262
+ body.head, body.base,
263
+ session_id=body.session_id, task_id=body.task_id
264
+ )
265
+
266
+
267
+ @router.get("/github/user")
268
+ async def github_user(request: Request):
269
+ r = _get_tool_router(request)
270
+ return await r.github.get_user()
271
+
272
+
273
+ @router.get("/github/issues")
274
+ async def github_issues(request: Request, owner: str, repo: str, state: str = "open"):
275
+ r = _get_tool_router(request)
276
+ issues = await r.github.read_issues(owner, repo, state)
277
+ return {"issues": issues, "count": len(issues)}
278
+
279
+
280
+ # ═══════════════════════════════════════════════════════════════════
281
+ # DEPLOYMENT ENDPOINTS
282
+ # ═══════════════════════════════════════════════════════════════════
283
+
284
+ @router.post("/deploy/vercel")
285
+ async def deploy_vercel(body: DeployVercelRequest, request: Request):
286
+ r = _get_tool_router(request)
287
+ return await r.deployment.deploy_vercel(
288
+ body.project_dir, body.project_name,
289
+ session_id=body.session_id, task_id=body.task_id
290
+ )
291
+
292
+
293
+ @router.post("/deploy/huggingface")
294
+ async def deploy_hf(body: DeployHFRequest, request: Request):
295
+ r = _get_tool_router(request)
296
+ return await r.deployment.deploy_hf_space(
297
+ body.repo_path, body.space_name,
298
+ session_id=body.session_id, task_id=body.task_id
299
+ )
300
+
301
+
302
+ # ══════════════════��════════════════════════════════════════════════
303
+ # AUTONOMOUS TASK ENDPOINT
304
+ # ═══════════════════════════════════════════════════════════════════
305
+
306
+ @router.post("/autonomous/run")
307
+ async def autonomous_run(body: AutonomousTaskRequest, request: Request):
308
+ """
309
+ Run a fully autonomous task.
310
+ The agent will plan, execute, self-repair, and deploy.
311
+ """
312
+ ws = getattr(request.app.state, "ws_manager", None)
313
+ ai_router = getattr(request.app.state, "ai_router", None)
314
+
315
+ from agents.autonomous_agent import AutonomousAgent
316
+ agent = AutonomousAgent(ws_manager=ws, ai_router=ai_router)
317
+
318
+ task_id = body.task_id or f"auto_{uuid.uuid4().hex[:8]}"
319
+
320
+ # Run in background and return task_id immediately
321
+ asyncio.create_task(
322
+ agent.run(
323
+ body.goal,
324
+ context=body.context,
325
+ session_id=body.session_id,
326
+ task_id=task_id,
327
+ max_steps=body.max_steps,
328
+ )
329
+ )
330
+
331
+ return {
332
+ "task_id": task_id,
333
+ "goal": body.goal,
334
+ "status": "executing",
335
+ "stream_url": f"/api/v1/tasks/{task_id}/stream",
336
+ "ws_url": f"/ws/tasks/{task_id}",
337
+ }
338
+
339
+
340
+ @router.post("/autonomous/run-sync")
341
+ async def autonomous_run_sync(body: AutonomousTaskRequest, request: Request):
342
+ """Run autonomous task synchronously (waits for completion)."""
343
+ ws = getattr(request.app.state, "ws_manager", None)
344
+ ai_router = getattr(request.app.state, "ai_router", None)
345
+
346
+ from agents.autonomous_agent import AutonomousAgent
347
+ agent = AutonomousAgent(ws_manager=ws, ai_router=ai_router)
348
+
349
+ task_id = body.task_id or f"auto_{uuid.uuid4().hex[:8]}"
350
+
351
+ result = await agent.run(
352
+ body.goal,
353
+ context=body.context,
354
+ session_id=body.session_id,
355
+ task_id=task_id,
356
+ max_steps=body.max_steps,
357
+ )
358
+
359
+ return {
360
+ "task_id": task_id,
361
+ "goal": body.goal,
362
+ "status": "complete",
363
+ "result": result,
364
+ }
365
+
366
+
367
+ # ─── Tool capabilities info ──────────────────────────────────────────────────
368
+
369
+ @router.get("/tools")
370
+ async def list_tools():
371
+ """List all available real tools."""
372
+ return {
373
+ "tools": {
374
+ "terminal": {
375
+ "terminal.run": "Execute shell command",
376
+ "terminal.sequence": "Run command sequence",
377
+ "terminal.run_with_repair": "Run with auto self-repair on error",
378
+ },
379
+ "filesystem": {
380
+ "fs.read": "Read file content",
381
+ "fs.write": "Write/create file",
382
+ "fs.patch": "Patch specific section of file",
383
+ "fs.delete": "Delete file",
384
+ "fs.move": "Move/rename file",
385
+ "fs.search": "Search file contents",
386
+ "fs.tree": "Get file tree",
387
+ "fs.list": "List directory",
388
+ },
389
+ "github": {
390
+ "github.clone": "Clone repository",
391
+ "github.create_repo": "Create new repository",
392
+ "github.commit_push": "Commit and push changes",
393
+ "github.create_branch": "Create new branch",
394
+ "github.create_pr": "Open pull request",
395
+ "github.read_issues": "Read open issues",
396
+ },
397
+ "deployment": {
398
+ "deploy.vercel": "Deploy to Vercel",
399
+ "deploy.hf": "Deploy to HuggingFace Spaces",
400
+ },
401
+ "autonomous": {
402
+ "autonomous.run": "Run fully autonomous task (async)",
403
+ "autonomous.run-sync": "Run fully autonomous task (sync)",
404
+ },
405
+ },
406
+ "workspace": WORKSPACE,
407
+ "capabilities": [
408
+ "Real terminal execution with streaming",
409
+ "File system read/write/patch/delete",
410
+ "GitHub clone/commit/push/PR creation",
411
+ "Self-repair loop (3 retries with AI analysis)",
412
+ "Vercel + HuggingFace deployment",
413
+ "Autonomous multi-step task execution",
414
+ ],
415
+ }
main.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
- πŸš€ GOD MODE+ V5 β€” Manus Β· Genspark Β· Devin
3
- Autonomous AI Operating System β€” Production-Grade Backend
4
- Version: 5.0.0
5
  """
6
 
7
  import asyncio
@@ -24,12 +24,13 @@ from slowapi.errors import RateLimitExceeded
24
 
25
  from api.routes import tasks, chat, memory, github, health
26
  from api.routes import connectors, agents as agents_router
 
27
  from api.websocket_manager import WebSocketManager
28
  from core.task_engine import TaskEngine
29
  from memory.db import init_db
30
 
31
- # ─── V5 AI Router ─────────────────────────────────────────────────────────────
32
- from ai_router.router_v5 import AIRouterV5
33
  from agents.orchestrator import GodAgentOrchestrator
34
  from agents.chat_agent import ChatAgent
35
  from agents.planner_agent import PlannerAgent
@@ -41,6 +42,7 @@ from agents.deploy_agent import DeployAgent
41
  from agents.workflow_agent import WorkflowAgent
42
  from agents.sandbox_agent import SandboxAgent
43
  from agents.ui_agent import UIAgent
 
44
  from connectors.manager import ConnectorManager
45
 
46
  # ─── Structured Logging ────────────────────────────────────────────────────────
@@ -60,13 +62,14 @@ limiter = Limiter(key_func=get_remote_address)
60
  # ─── Global Managers ──────────────────────────────────────────────────────────
61
  ws_manager = WebSocketManager()
62
  task_engine = TaskEngine(ws_manager)
63
- ai_router = AIRouterV5(ws_manager)
64
  connector_manager = ConnectorManager()
65
 
66
-
67
  # ─── Build God Agent Ecosystem ────────────────────────────────────────────────
68
  def build_orchestrator() -> GodAgentOrchestrator:
69
  orchestrator = GodAgentOrchestrator(ws_manager=ws_manager, ai_router=ai_router)
 
 
70
  orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
71
  orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
72
  orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
@@ -77,33 +80,35 @@ def build_orchestrator() -> GodAgentOrchestrator:
77
  orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
78
  orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
79
  orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
80
- log.info("πŸ€– God Agent Ecosystem V5 initialized", agents=10)
81
- return orchestrator
82
 
 
 
83
 
84
  orchestrator = build_orchestrator()
85
 
86
 
87
  @asynccontextmanager
88
  async def lifespan(app: FastAPI):
89
- log.info("πŸš€ Starting GOD MODE+ V5 AI Operating System...")
 
90
  await init_db()
91
  await task_engine.start()
92
  asyncio.create_task(ws_manager.heartbeat_loop())
93
- providers = ai_router.get_provider_status()
94
- active = [p for p, s in providers.items() if s.get("available")]
95
- log.info("βœ… GOD MODE+ V5 ready", active_providers=active)
96
  yield
97
- log.info("πŸ›‘ Shutting down V5...")
98
  await task_engine.stop()
99
  log.info("βœ… Shutdown complete")
100
 
101
 
102
  # ─── FastAPI App ───────────────────────────────────────────────────────────────
103
  app = FastAPI(
104
- title="πŸ€– GOD MODE+ V5 β€” Manus Β· Genspark Β· Devin",
105
- description="Autonomous AI Engineering Platform β€” Gemini Β· SambaNova Β· GitHub Models Β· OpenAI Β· Groq",
106
- version="5.0.0",
107
  lifespan=lifespan,
108
  docs_url="/api/docs",
109
  redoc_url="/api/redoc",
@@ -112,6 +117,7 @@ app = FastAPI(
112
  app.state.limiter = limiter
113
  app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
114
 
 
115
  app.state.ws_manager = ws_manager
116
  app.state.task_engine = task_engine
117
  app.state.ai_router = ai_router
@@ -139,13 +145,40 @@ async def log_requests(request: Request, call_next):
139
 
140
 
141
  # ─── REST API Routers ──────────────────────────────────────────────────────────
142
- app.include_router(health.router, prefix="/api/v1", tags=["health"])
143
- app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
144
- app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
145
- app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
146
- app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
147
- app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
148
- app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
 
151
  # ─── WebSocket Endpoints ───────────────────────────────────────────────────────
@@ -185,6 +218,7 @@ async def ws_chat(websocket: WebSocket, session_id: str):
185
  if msg.get("type") == "ping":
186
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
187
  elif msg.get("type") == "chat_message":
 
188
  asyncio.create_task(
189
  orchestrator.orchestrate(
190
  user_message=msg.get("content", ""),
@@ -193,8 +227,12 @@ async def ws_chat(websocket: WebSocket, session_id: str):
193
  )
194
  )
195
  elif msg.get("type") == "task_message":
 
196
  from core.models import TaskCreateRequest
197
- req = TaskCreateRequest(goal=msg.get("content", ""), session_id=session_id)
 
 
 
198
  asyncio.create_task(task_engine.submit(req))
199
  except WebSocketDisconnect:
200
  ws_manager.disconnect(websocket, room=f"chat:{session_id}")
@@ -210,14 +248,17 @@ async def ws_agent_status(websocket: WebSocket):
210
  if msg.get("type") == "ping":
211
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
212
  elif msg.get("type") == "get_status":
213
- await websocket.send_json({"type": "agent_status", "data": orchestrator.get_status()})
 
 
 
214
  except WebSocketDisconnect:
215
  ws_manager.disconnect(websocket, room="agent_status")
216
 
217
 
218
  @app.websocket("/ws/sandbox/{session_id}")
219
  async def ws_sandbox(websocket: WebSocket, session_id: str):
220
- """Live sandbox terminal stream β€” V5 with timeout guard."""
221
  await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
222
  sandbox = orchestrator.get_agent("sandbox")
223
  try:
@@ -228,15 +269,7 @@ async def ws_sandbox(websocket: WebSocket, session_id: str):
228
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
229
  elif msg.get("type") == "execute" and sandbox:
230
  cmd = msg.get("command", "")
231
- try:
232
- result = await asyncio.wait_for(
233
- sandbox.execute(cmd, session_id=session_id),
234
- timeout=30.0
235
- )
236
- except asyncio.TimeoutError:
237
- result = "⏱️ Command timed out after 30 seconds"
238
- except Exception as e:
239
- result = f"❌ Error: {str(e)}"
240
  await websocket.send_json({
241
  "type": "terminal_output",
242
  "command": cmd,
@@ -251,14 +284,11 @@ async def ws_sandbox(websocket: WebSocket, session_id: str):
251
  @app.get("/")
252
  async def root():
253
  cs = connector_manager.get_summary()
254
- providers = ai_router.get_provider_status()
255
- active_providers = [p for p, s in providers.items() if s.get("available")]
256
  return {
257
- "name": "πŸ€– GOD MODE+ V5 β€” Manus Β· Genspark Β· Devin",
258
- "version": "5.0.0",
259
  "status": "operational",
260
- "mode": "god_mode_plus_v5",
261
- "ai_providers": active_providers,
262
  "agents": orchestrator.get_status()["agents"],
263
  "connectors": {
264
  "connected": cs["connected"],
@@ -266,4 +296,21 @@ async def root():
266
  "ai_ready": cs["ai_ready"],
267
  },
268
  "docs": "/api/docs",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  }
 
1
  """
2
+ πŸš€ GOD MODE+ Autonomous AI Operating System
3
+ Devin + Manus + Genspark Style β€” Production-Grade Backend
4
+ Version: 4.0.0 β€” Real Execution Engine + Mission Control UI
5
  """
6
 
7
  import asyncio
 
24
 
25
  from api.routes import tasks, chat, memory, github, health
26
  from api.routes import connectors, agents as agents_router
27
+ from api.routes import execution as execution_router
28
  from api.websocket_manager import WebSocketManager
29
  from core.task_engine import TaskEngine
30
  from memory.db import init_db
31
 
32
+ # ─── God Mode Agents ───────────────────────────────────────────────────────────
33
+ from ai_router.router import AIRouter
34
  from agents.orchestrator import GodAgentOrchestrator
35
  from agents.chat_agent import ChatAgent
36
  from agents.planner_agent import PlannerAgent
 
42
  from agents.workflow_agent import WorkflowAgent
43
  from agents.sandbox_agent import SandboxAgent
44
  from agents.ui_agent import UIAgent
45
+ from agents.autonomous_agent import AutonomousAgent
46
  from connectors.manager import ConnectorManager
47
 
48
  # ─── Structured Logging ────────────────────────────────────────────────────────
 
62
  # ─── Global Managers ──────────────────────────────────────────────────────────
63
  ws_manager = WebSocketManager()
64
  task_engine = TaskEngine(ws_manager)
65
+ ai_router = AIRouter(ws_manager)
66
  connector_manager = ConnectorManager()
67
 
 
68
  # ─── Build God Agent Ecosystem ────────────────────────────────────────────────
69
  def build_orchestrator() -> GodAgentOrchestrator:
70
  orchestrator = GodAgentOrchestrator(ws_manager=ws_manager, ai_router=ai_router)
71
+
72
+ # Register all specialized agents
73
  orchestrator.register_agent("chat", ChatAgent(ws_manager, ai_router))
74
  orchestrator.register_agent("planner", PlannerAgent(ws_manager, ai_router))
75
  orchestrator.register_agent("coding", CodingAgent(ws_manager, ai_router))
 
80
  orchestrator.register_agent("workflow", WorkflowAgent(ws_manager, ai_router))
81
  orchestrator.register_agent("sandbox", SandboxAgent(ws_manager, ai_router))
82
  orchestrator.register_agent("ui", UIAgent(ws_manager, ai_router))
83
+ orchestrator.register_agent("autonomous", AutonomousAgent(ws_manager, ai_router))
 
84
 
85
+ log.info("πŸ€– God Agent Ecosystem initialized", agents=10)
86
+ return orchestrator
87
 
88
  orchestrator = build_orchestrator()
89
 
90
 
91
  @asynccontextmanager
92
  async def lifespan(app: FastAPI):
93
+ """Startup + Shutdown lifecycle."""
94
+ log.info("πŸš€ Starting GOD MODE+ AI Operating System...")
95
  await init_db()
96
  await task_engine.start()
97
  asyncio.create_task(ws_manager.heartbeat_loop())
98
+ log.info("βœ… GOD MODE+ Platform ready β€” All agents online")
99
+ log.info("πŸ€– Agents: Chat, Planner, Coding, Debug, Memory, Connector, Deploy, Workflow, Sandbox, UI")
100
+ log.info("🌐 AI Router: OpenAI β†’ Groq β†’ Cerebras β†’ OpenRouter β†’ Anthropic")
101
  yield
102
+ log.info("πŸ›‘ Shutting down...")
103
  await task_engine.stop()
104
  log.info("βœ… Shutdown complete")
105
 
106
 
107
  # ─── FastAPI App ───────────────────────────────────────────────────────────────
108
  app = FastAPI(
109
+ title="πŸ€– GOD MODE+ AI Operating System",
110
+ description="Devin + Manus + Genspark Autonomous AI Engineering Platform",
111
+ version="3.0.0",
112
  lifespan=lifespan,
113
  docs_url="/api/docs",
114
  redoc_url="/api/redoc",
 
117
  app.state.limiter = limiter
118
  app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
119
 
120
+ # ─── Share state ───────────────────────────────────────────────────────────────
121
  app.state.ws_manager = ws_manager
122
  app.state.task_engine = task_engine
123
  app.state.ai_router = ai_router
 
145
 
146
 
147
  # ─── REST API Routers ──────────────────────────────────────────────────────────
148
+ app.include_router(health.router, prefix="/api/v1", tags=["health"])
149
+ app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
150
+ app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
151
+ app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
152
+ app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
153
+ app.include_router(connectors.router, prefix="/api/v1/connectors", tags=["connectors"])
154
+ app.include_router(agents_router.router, prefix="/api/v1/agents", tags=["agents"])
155
+ app.include_router(execution_router.router, prefix="/api/v1/exec", tags=["execution"])
156
+
157
+ # ─── WebSocket: autonomous execution stream ────────────────────────────────
158
+ @app.websocket("/ws/exec/{session_id}")
159
+ async def ws_exec(websocket: WebSocket, session_id: str):
160
+ """Live execution streaming for autonomous tasks."""
161
+ await ws_manager.connect(websocket, room=f"exec:{session_id}")
162
+ try:
163
+ while True:
164
+ data = await websocket.receive_text()
165
+ msg = json.loads(data)
166
+ if msg.get("type") == "ping":
167
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
168
+ elif msg.get("type") == "run":
169
+ from agents.autonomous_agent import AutonomousAgent
170
+ agent = AutonomousAgent(ws_manager=ws_manager, ai_router=ai_router)
171
+ asyncio.create_task(
172
+ agent.run(
173
+ msg.get("goal", ""),
174
+ context=msg.get("context", {}),
175
+ session_id=session_id,
176
+ task_id=session_id,
177
+ max_steps=msg.get("max_steps", 20),
178
+ )
179
+ )
180
+ except WebSocketDisconnect:
181
+ ws_manager.disconnect(websocket, room=f"exec:{session_id}")
182
 
183
 
184
  # ─── WebSocket Endpoints ───────────────────────────────────────────────────────
 
218
  if msg.get("type") == "ping":
219
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
220
  elif msg.get("type") == "chat_message":
221
+ # Route through God Agent Orchestrator
222
  asyncio.create_task(
223
  orchestrator.orchestrate(
224
  user_message=msg.get("content", ""),
 
227
  )
228
  )
229
  elif msg.get("type") == "task_message":
230
+ # Create autonomous task via task engine
231
  from core.models import TaskCreateRequest
232
+ req = TaskCreateRequest(
233
+ goal=msg.get("content", ""),
234
+ session_id=session_id,
235
+ )
236
  asyncio.create_task(task_engine.submit(req))
237
  except WebSocketDisconnect:
238
  ws_manager.disconnect(websocket, room=f"chat:{session_id}")
 
248
  if msg.get("type") == "ping":
249
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
250
  elif msg.get("type") == "get_status":
251
+ await websocket.send_json({
252
+ "type": "agent_status",
253
+ "data": orchestrator.get_status(),
254
+ })
255
  except WebSocketDisconnect:
256
  ws_manager.disconnect(websocket, room="agent_status")
257
 
258
 
259
  @app.websocket("/ws/sandbox/{session_id}")
260
  async def ws_sandbox(websocket: WebSocket, session_id: str):
261
+ """Live sandbox terminal stream."""
262
  await ws_manager.connect(websocket, room=f"sandbox:{session_id}")
263
  sandbox = orchestrator.get_agent("sandbox")
264
  try:
 
269
  await websocket.send_json({"type": "pong", "timestamp": time.time()})
270
  elif msg.get("type") == "execute" and sandbox:
271
  cmd = msg.get("command", "")
272
+ result = await sandbox.execute(cmd, session_id=session_id)
 
 
 
 
 
 
 
 
273
  await websocket.send_json({
274
  "type": "terminal_output",
275
  "command": cmd,
 
284
  @app.get("/")
285
  async def root():
286
  cs = connector_manager.get_summary()
 
 
287
  return {
288
+ "name": "πŸ€– GOD MODE+ AI Operating System",
289
+ "version": "3.0.0",
290
  "status": "operational",
291
+ "mode": "god_mode_plus",
 
292
  "agents": orchestrator.get_status()["agents"],
293
  "connectors": {
294
  "connected": cs["connected"],
 
296
  "ai_ready": cs["ai_ready"],
297
  },
298
  "docs": "/api/docs",
299
+ "websockets": [
300
+ "/ws/tasks/{task_id}",
301
+ "/ws/logs",
302
+ "/ws/chat/{session_id}",
303
+ "/ws/agent/status",
304
+ "/ws/sandbox/{session_id}",
305
+ ],
306
+ "phases_complete": [
307
+ "Phase 1: God Agent Orchestrator",
308
+ "Phase 2: Sandbox Agent",
309
+ "Phase 3: Connector System",
310
+ "Phase 4: Autonomous Coding Engine",
311
+ "Phase 5: Memory System",
312
+ "Phase 6: Real-time Streaming",
313
+ "Phase 7: Workflow Factor OS",
314
+ "Phase 9: Multi-Model AI Router",
315
+ ],
316
  }
tools/real_executor.py ADDED
@@ -0,0 +1,1027 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ REAL EXECUTION ENGINE β€” God Mode+ v4.0
3
+ Devin/Manus-style autonomous execution with:
4
+ - Real terminal execution with streaming
5
+ - File system control (read/write/patch/delete/move/search/tree)
6
+ - GitHub autonomy (clone/commit/push/PR/branch)
7
+ - Self-repair loop (error analysis + auto-patch + retry)
8
+ - Deployment automation (Vercel/HuggingFace)
9
+ - Tool call router
10
+ """
11
+
12
+ import asyncio
13
+ import json
14
+ import os
15
+ import re
16
+ import subprocess
17
+ import tempfile
18
+ import time
19
+ import uuid
20
+ from pathlib import Path
21
+ from typing import Any, Dict, List, Optional, Tuple
22
+
23
+ import httpx
24
+ import structlog
25
+
26
+ log = structlog.get_logger()
27
+
28
+ WORKSPACE = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
29
+ GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
30
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
31
+ VERCEL_TOKEN = os.environ.get("VERCEL_TOKEN", "")
32
+
33
+ os.makedirs(WORKSPACE, exist_ok=True)
34
+
35
+ # ─── BLOCKED COMMANDS (Security) ────────────────────────────────────────────
36
+ BLOCKED_PATTERNS = [
37
+ r"rm\s+-rf\s+/[^t]", # rm -rf / but allow /tmp
38
+ r":\(\)\s*\{", # fork bomb
39
+ r"mkfs\.", # format filesystem
40
+ r"dd\s+if=/dev/", # disk destruction
41
+ r"shutdown",
42
+ r"reboot",
43
+ r"halt",
44
+ r"poweroff",
45
+ r"chmod\s+777\s+/",
46
+ r"wget.*\|\s*bash",
47
+ r"curl.*\|\s*bash",
48
+ ]
49
+
50
+ def is_blocked(cmd: str) -> bool:
51
+ for pattern in BLOCKED_PATTERNS:
52
+ if re.search(pattern, cmd, re.IGNORECASE):
53
+ return True
54
+ return False
55
+
56
+
57
+ # ═══════════════════════════════════════════════════════════════════
58
+ # TERMINAL EXECUTION ENGINE
59
+ # ═══════════════════════════════════════════════════════════════════
60
+
61
+ class TerminalEngine:
62
+ """Real shell execution with streaming output."""
63
+
64
+ def __init__(self, ws_manager=None):
65
+ self.ws = ws_manager
66
+ self._sessions: Dict[str, str] = {} # session_id -> cwd
67
+
68
+ def get_cwd(self, session_id: str) -> str:
69
+ return self._sessions.get(session_id, WORKSPACE)
70
+
71
+ def set_cwd(self, session_id: str, cwd: str):
72
+ if os.path.isdir(cwd):
73
+ self._sessions[session_id] = cwd
74
+
75
+ async def execute(
76
+ self,
77
+ command: str,
78
+ session_id: str = "",
79
+ task_id: str = "",
80
+ cwd: Optional[str] = None,
81
+ timeout: int = 60,
82
+ env_vars: Dict[str, str] = {},
83
+ ) -> Dict[str, Any]:
84
+ """Execute command with real streaming output."""
85
+
86
+ if is_blocked(command):
87
+ return {
88
+ "success": False,
89
+ "output": f"❌ BLOCKED: Dangerous command rejected: {command[:80]}",
90
+ "exit_code": -1,
91
+ "command": command,
92
+ }
93
+
94
+ work_dir = cwd or self.get_cwd(session_id)
95
+ os.makedirs(work_dir, exist_ok=True)
96
+
97
+ # Handle cd specially
98
+ if command.strip().startswith("cd "):
99
+ new_dir = command.strip()[3:].strip()
100
+ if not os.path.isabs(new_dir):
101
+ new_dir = os.path.join(work_dir, new_dir)
102
+ new_dir = os.path.normpath(new_dir)
103
+ if os.path.isdir(new_dir):
104
+ self.set_cwd(session_id, new_dir)
105
+ return {"success": True, "output": f"πŸ“‚ Changed to: {new_dir}", "exit_code": 0, "command": command, "cwd": new_dir}
106
+ else:
107
+ return {"success": False, "output": f"❌ Directory not found: {new_dir}", "exit_code": 1, "command": command}
108
+
109
+ # Build environment
110
+ env = {**os.environ, **env_vars}
111
+
112
+ # Emit start event
113
+ if self.ws and task_id:
114
+ await self.ws.emit(task_id, "terminal_start", {
115
+ "command": command[:200],
116
+ "cwd": work_dir,
117
+ "session_id": session_id,
118
+ }, session_id=session_id)
119
+
120
+ output_lines = []
121
+ exit_code = 0
122
+
123
+ try:
124
+ proc = await asyncio.create_subprocess_shell(
125
+ command,
126
+ stdout=asyncio.subprocess.PIPE,
127
+ stderr=asyncio.subprocess.STDOUT,
128
+ cwd=work_dir,
129
+ env=env,
130
+ )
131
+
132
+ # Stream output line by line
133
+ async def read_stream():
134
+ while True:
135
+ try:
136
+ line = await asyncio.wait_for(proc.stdout.readline(), timeout=timeout)
137
+ if not line:
138
+ break
139
+ decoded = line.decode("utf-8", errors="replace").rstrip()
140
+ output_lines.append(decoded)
141
+
142
+ # Stream to websocket
143
+ if self.ws and (task_id or session_id):
144
+ await self.ws.emit(
145
+ task_id or "logs",
146
+ "terminal_output",
147
+ {"line": decoded, "command": command[:80]},
148
+ session_id=session_id,
149
+ )
150
+ except asyncio.TimeoutError:
151
+ proc.kill()
152
+ output_lines.append(f"\n⏱️ Command timed out after {timeout}s")
153
+ break
154
+
155
+ await read_stream()
156
+ try:
157
+ exit_code = await asyncio.wait_for(proc.wait(), timeout=5)
158
+ except asyncio.TimeoutError:
159
+ proc.kill()
160
+ exit_code = -1
161
+
162
+ except Exception as e:
163
+ output_lines.append(f"❌ Execution error: {str(e)}")
164
+ exit_code = -1
165
+
166
+ full_output = "\n".join(output_lines)
167
+
168
+ # Emit completion
169
+ if self.ws and task_id:
170
+ await self.ws.emit(task_id, "terminal_done", {
171
+ "command": command[:80],
172
+ "exit_code": exit_code,
173
+ "success": exit_code == 0,
174
+ "lines": len(output_lines),
175
+ }, session_id=session_id)
176
+
177
+ return {
178
+ "success": exit_code == 0,
179
+ "output": full_output[:8000],
180
+ "exit_code": exit_code,
181
+ "command": command,
182
+ "cwd": work_dir,
183
+ }
184
+
185
+ async def run_sequence(
186
+ self,
187
+ commands: List[str],
188
+ session_id: str = "",
189
+ task_id: str = "",
190
+ stop_on_error: bool = True,
191
+ ) -> List[Dict]:
192
+ """Run a sequence of commands, streaming each."""
193
+ results = []
194
+ for cmd in commands:
195
+ result = await self.execute(cmd, session_id=session_id, task_id=task_id)
196
+ results.append(result)
197
+ if not result["success"] and stop_on_error:
198
+ break
199
+ return results
200
+
201
+
202
+ # ═══════════════════════════════════════════════════════════════════
203
+ # FILESYSTEM CONTROL ENGINE
204
+ # ═══════════════════════════════════════════════════════════════════
205
+
206
+ class FilesystemEngine:
207
+ """Full file system control."""
208
+
209
+ def __init__(self, ws_manager=None):
210
+ self.ws = ws_manager
211
+
212
+ def _resolve(self, path: str, base: str = WORKSPACE) -> str:
213
+ if os.path.isabs(path):
214
+ resolved = os.path.normpath(path)
215
+ else:
216
+ resolved = os.path.normpath(os.path.join(base, path))
217
+ return resolved
218
+
219
+ async def read_file(self, path: str, session_id: str = "", task_id: str = "") -> Dict:
220
+ fpath = self._resolve(path)
221
+ try:
222
+ with open(fpath, "r", encoding="utf-8", errors="replace") as f:
223
+ content = f.read()
224
+ lines = content.split("\n")
225
+ result = {
226
+ "success": True,
227
+ "path": fpath,
228
+ "content": content,
229
+ "lines": len(lines),
230
+ "size": len(content),
231
+ "language": self._detect_language(fpath),
232
+ }
233
+ if self.ws and task_id:
234
+ await self.ws.emit(task_id, "file_read", {"path": fpath, "lines": len(lines)}, session_id=session_id)
235
+ return result
236
+ except FileNotFoundError:
237
+ return {"success": False, "error": f"File not found: {path}"}
238
+ except Exception as e:
239
+ return {"success": False, "error": str(e)}
240
+
241
+ async def write_file(self, path: str, content: str, session_id: str = "", task_id: str = "") -> Dict:
242
+ fpath = self._resolve(path)
243
+ os.makedirs(os.path.dirname(fpath), exist_ok=True)
244
+ try:
245
+ with open(fpath, "w", encoding="utf-8") as f:
246
+ f.write(content)
247
+ lines = len(content.split("\n"))
248
+ if self.ws and task_id:
249
+ await self.ws.emit(task_id, "file_written", {
250
+ "path": fpath,
251
+ "filename": os.path.basename(fpath),
252
+ "size": len(content),
253
+ "lines": lines,
254
+ "language": self._detect_language(fpath),
255
+ }, session_id=session_id)
256
+ return {"success": True, "path": fpath, "size": len(content), "lines": lines}
257
+ except Exception as e:
258
+ return {"success": False, "error": str(e)}
259
+
260
+ async def patch_file(self, path: str, old_content: str, new_content: str, session_id: str = "", task_id: str = "") -> Dict:
261
+ """Patch a specific section of a file."""
262
+ fpath = self._resolve(path)
263
+ try:
264
+ with open(fpath, "r", encoding="utf-8") as f:
265
+ current = f.read()
266
+ if old_content not in current:
267
+ return {"success": False, "error": "Patch target not found in file"}
268
+ patched = current.replace(old_content, new_content, 1)
269
+ with open(fpath, "w", encoding="utf-8") as f:
270
+ f.write(patched)
271
+
272
+ # Emit diff event
273
+ if self.ws and task_id:
274
+ await self.ws.emit(task_id, "file_patched", {
275
+ "path": fpath,
276
+ "filename": os.path.basename(fpath),
277
+ "old_snippet": old_content[:100],
278
+ "new_snippet": new_content[:100],
279
+ }, session_id=session_id)
280
+
281
+ return {
282
+ "success": True,
283
+ "path": fpath,
284
+ "diff": {
285
+ "removed": old_content.split("\n")[:5],
286
+ "added": new_content.split("\n")[:5],
287
+ }
288
+ }
289
+ except Exception as e:
290
+ return {"success": False, "error": str(e)}
291
+
292
+ async def delete_file(self, path: str, session_id: str = "", task_id: str = "") -> Dict:
293
+ fpath = self._resolve(path)
294
+ try:
295
+ os.remove(fpath)
296
+ if self.ws and task_id:
297
+ await self.ws.emit(task_id, "file_deleted", {"path": fpath}, session_id=session_id)
298
+ return {"success": True, "path": fpath}
299
+ except Exception as e:
300
+ return {"success": False, "error": str(e)}
301
+
302
+ async def move_file(self, src: str, dst: str, session_id: str = "", task_id: str = "") -> Dict:
303
+ import shutil
304
+ src_path = self._resolve(src)
305
+ dst_path = self._resolve(dst)
306
+ try:
307
+ os.makedirs(os.path.dirname(dst_path), exist_ok=True)
308
+ shutil.move(src_path, dst_path)
309
+ if self.ws and task_id:
310
+ await self.ws.emit(task_id, "file_moved", {"from": src_path, "to": dst_path}, session_id=session_id)
311
+ return {"success": True, "from": src_path, "to": dst_path}
312
+ except Exception as e:
313
+ return {"success": False, "error": str(e)}
314
+
315
+ async def search_files(self, query: str, path: str = "", file_pattern: str = "*") -> Dict:
316
+ """Search file contents with grep."""
317
+ search_path = self._resolve(path) if path else WORKSPACE
318
+ try:
319
+ result = subprocess.run(
320
+ ["grep", "-r", "-l", "--include", file_pattern, query, search_path],
321
+ capture_output=True, text=True, timeout=10
322
+ )
323
+ files = [f for f in result.stdout.strip().split("\n") if f]
324
+ return {"success": True, "files": files, "count": len(files), "query": query}
325
+ except Exception as e:
326
+ return {"success": False, "error": str(e), "files": []}
327
+
328
+ async def tree(self, path: str = "", max_depth: int = 4) -> Dict:
329
+ """Get file tree of workspace."""
330
+ root = self._resolve(path) if path else WORKSPACE
331
+ try:
332
+ result = subprocess.run(
333
+ ["find", root, "-maxdepth", str(max_depth),
334
+ "!", "-path", "*/node_modules/*",
335
+ "!", "-path", "*/__pycache__/*",
336
+ "!", "-path", "*/.git/*",
337
+ "!", "-path", "*/.next/*",
338
+ "-type", "f"],
339
+ capture_output=True, text=True, timeout=10
340
+ )
341
+ files = [os.path.relpath(f, root) for f in result.stdout.strip().split("\n") if f]
342
+ return {"success": True, "root": root, "files": files[:200], "count": len(files)}
343
+ except Exception as e:
344
+ return {"success": False, "error": str(e), "files": []}
345
+
346
+ async def list_dir(self, path: str = "") -> Dict:
347
+ target = self._resolve(path) if path else WORKSPACE
348
+ try:
349
+ entries = []
350
+ for item in sorted(os.listdir(target)):
351
+ full = os.path.join(target, item)
352
+ entries.append({
353
+ "name": item,
354
+ "type": "dir" if os.path.isdir(full) else "file",
355
+ "size": os.path.getsize(full) if os.path.isfile(full) else 0,
356
+ })
357
+ return {"success": True, "path": target, "entries": entries}
358
+ except Exception as e:
359
+ return {"success": False, "error": str(e), "entries": []}
360
+
361
+ def _detect_language(self, path: str) -> str:
362
+ ext_map = {
363
+ ".py": "python", ".ts": "typescript", ".tsx": "tsx",
364
+ ".js": "javascript", ".jsx": "jsx", ".go": "go",
365
+ ".rs": "rust", ".java": "java", ".cpp": "cpp",
366
+ ".c": "c", ".sh": "bash", ".yaml": "yaml",
367
+ ".yml": "yaml", ".json": "json", ".md": "markdown",
368
+ ".html": "html", ".css": "css", ".sql": "sql",
369
+ ".dockerfile": "dockerfile", ".toml": "toml",
370
+ }
371
+ ext = Path(path).suffix.lower()
372
+ return ext_map.get(ext, "text")
373
+
374
+
375
+ # ═══════════════════════════════════════════════════════════════════
376
+ # GITHUB AUTONOMY ENGINE
377
+ # ═���═════════════════════════════════════════════════════════════════
378
+
379
+ class GitHubEngine:
380
+ """Full GitHub autonomy - clone, commit, push, PR, branch."""
381
+
382
+ def __init__(self, token: str = "", ws_manager=None):
383
+ self.token = token or GITHUB_TOKEN
384
+ self.ws = ws_manager
385
+ self.base_url = "https://api.github.com"
386
+ self.headers = {
387
+ "Authorization": f"token {self.token}",
388
+ "Accept": "application/vnd.github.v3+json",
389
+ }
390
+
391
+ async def _api(self, method: str, path: str, data: Dict = {}) -> Dict:
392
+ async with httpx.AsyncClient(timeout=30) as client:
393
+ url = f"{self.base_url}{path}"
394
+ if method == "GET":
395
+ r = await client.get(url, headers=self.headers)
396
+ elif method == "POST":
397
+ r = await client.post(url, json=data, headers=self.headers)
398
+ elif method == "PATCH":
399
+ r = await client.patch(url, json=data, headers=self.headers)
400
+ elif method == "PUT":
401
+ r = await client.put(url, json=data, headers=self.headers)
402
+ elif method == "DELETE":
403
+ r = await client.delete(url, headers=self.headers)
404
+ else:
405
+ return {"error": f"Unknown method: {method}"}
406
+ try:
407
+ return r.json()
408
+ except Exception:
409
+ return {"error": r.text, "status": r.status_code}
410
+
411
+ async def get_user(self) -> Dict:
412
+ return await self._api("GET", "/user")
413
+
414
+ async def clone_repo(
415
+ self,
416
+ repo_url: str,
417
+ target_dir: str = "",
418
+ session_id: str = "",
419
+ task_id: str = "",
420
+ ) -> Dict:
421
+ """Clone a GitHub repository into workspace."""
422
+ if not target_dir:
423
+ repo_name = repo_url.rstrip("/").split("/")[-1].replace(".git", "")
424
+ target_dir = os.path.join(WORKSPACE, repo_name)
425
+
426
+ # Inject token into URL if available
427
+ if self.token and "github.com" in repo_url:
428
+ repo_url = repo_url.replace("https://", f"https://x-access-token:{self.token}@")
429
+
430
+ if self.ws and task_id:
431
+ await self.ws.emit(task_id, "github_op", {
432
+ "op": "clone",
433
+ "repo": repo_url.split("@")[-1] if "@" in repo_url else repo_url,
434
+ "target": target_dir,
435
+ }, session_id=session_id)
436
+
437
+ try:
438
+ proc = await asyncio.create_subprocess_exec(
439
+ "git", "clone", "--depth", "50", repo_url, target_dir,
440
+ stdout=asyncio.subprocess.PIPE,
441
+ stderr=asyncio.subprocess.PIPE,
442
+ )
443
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=120)
444
+ success = proc.returncode == 0
445
+ return {
446
+ "success": success,
447
+ "path": target_dir,
448
+ "output": (stdout or stderr).decode("utf-8", errors="replace")[:1000],
449
+ }
450
+ except Exception as e:
451
+ return {"success": False, "error": str(e)}
452
+
453
+ async def create_repo(
454
+ self,
455
+ name: str,
456
+ description: str = "",
457
+ private: bool = False,
458
+ auto_init: bool = True,
459
+ session_id: str = "",
460
+ task_id: str = "",
461
+ ) -> Dict:
462
+ """Create a new GitHub repository."""
463
+ data = {
464
+ "name": name,
465
+ "description": description,
466
+ "private": private,
467
+ "auto_init": auto_init,
468
+ }
469
+ if self.ws and task_id:
470
+ await self.ws.emit(task_id, "github_op", {"op": "create_repo", "name": name}, session_id=session_id)
471
+
472
+ result = await self._api("POST", "/user/repos", data)
473
+ return {
474
+ "success": "html_url" in result,
475
+ "url": result.get("html_url", ""),
476
+ "clone_url": result.get("clone_url", ""),
477
+ "ssh_url": result.get("ssh_url", ""),
478
+ "name": result.get("name", name),
479
+ "error": result.get("message", ""),
480
+ }
481
+
482
+ async def commit_and_push(
483
+ self,
484
+ repo_path: str,
485
+ message: str,
486
+ branch: str = "main",
487
+ session_id: str = "",
488
+ task_id: str = "",
489
+ ) -> Dict:
490
+ """Stage all changes, commit, and push."""
491
+ commands = [
492
+ f"git -C {repo_path} add -A",
493
+ f'git -C {repo_path} commit -m "{message}" --allow-empty',
494
+ f"git -C {repo_path} push origin {branch} --force-with-lease 2>&1 || git -C {repo_path} push origin {branch}",
495
+ ]
496
+
497
+ if self.ws and task_id:
498
+ await self.ws.emit(task_id, "github_op", {
499
+ "op": "commit_push",
500
+ "message": message,
501
+ "branch": branch,
502
+ }, session_id=session_id)
503
+
504
+ results = []
505
+ for cmd in commands:
506
+ proc = await asyncio.create_subprocess_shell(
507
+ cmd,
508
+ stdout=asyncio.subprocess.PIPE,
509
+ stderr=asyncio.subprocess.PIPE,
510
+ env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
511
+ )
512
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=60)
513
+ out = (stdout or b"").decode("utf-8", errors="replace")
514
+ err = (stderr or b"").decode("utf-8", errors="replace")
515
+ results.append({
516
+ "cmd": cmd.split("'")[0][:60],
517
+ "success": proc.returncode == 0,
518
+ "output": (out + err)[:500],
519
+ })
520
+
521
+ return {
522
+ "success": all(r["success"] for r in results),
523
+ "steps": results,
524
+ "message": message,
525
+ "branch": branch,
526
+ }
527
+
528
+ async def create_branch(
529
+ self,
530
+ repo_path: str,
531
+ branch_name: str,
532
+ from_branch: str = "main",
533
+ session_id: str = "",
534
+ task_id: str = "",
535
+ ) -> Dict:
536
+ cmd = f"git -C {repo_path} checkout -b {branch_name} origin/{from_branch} 2>/dev/null || git -C {repo_path} checkout -b {branch_name}"
537
+ proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
538
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
539
+ return {"success": proc.returncode == 0, "branch": branch_name}
540
+
541
+ async def create_pr(
542
+ self,
543
+ owner: str,
544
+ repo: str,
545
+ title: str,
546
+ body: str,
547
+ head: str,
548
+ base: str = "main",
549
+ session_id: str = "",
550
+ task_id: str = "",
551
+ ) -> Dict:
552
+ """Create a Pull Request."""
553
+ data = {"title": title, "body": body, "head": head, "base": base}
554
+ if self.ws and task_id:
555
+ await self.ws.emit(task_id, "github_op", {"op": "create_pr", "title": title}, session_id=session_id)
556
+ result = await self._api("POST", f"/repos/{owner}/{repo}/pulls", data)
557
+ return {
558
+ "success": "html_url" in result,
559
+ "url": result.get("html_url", ""),
560
+ "number": result.get("number"),
561
+ "error": result.get("message", ""),
562
+ }
563
+
564
+ async def get_repo_contents(self, owner: str, repo: str, path: str = "") -> Dict:
565
+ result = await self._api("GET", f"/repos/{owner}/{repo}/contents/{path}")
566
+ return result
567
+
568
+ async def read_issues(self, owner: str, repo: str, state: str = "open") -> List[Dict]:
569
+ result = await self._api("GET", f"/repos/{owner}/{repo}/issues?state={state}&per_page=10")
570
+ if isinstance(result, list):
571
+ return [{"number": i.get("number"), "title": i.get("title"), "body": (i.get("body") or "")[:200]} for i in result]
572
+ return []
573
+
574
+
575
+ # ═══════════════════════════════════════════════════════════════════
576
+ # SELF-REPAIR LOOP ENGINE
577
+ # ═══════════════════════════════════════════════════════════════════
578
+
579
+ class SelfRepairEngine:
580
+ """
581
+ Devin-style self-repair loop:
582
+ Run β†’ See error β†’ Analyze β†’ Patch β†’ Retry β†’ Pass
583
+ """
584
+
585
+ def __init__(self, terminal: TerminalEngine, filesystem: FilesystemEngine, ai_router=None, ws_manager=None):
586
+ self.terminal = terminal
587
+ self.filesystem = filesystem
588
+ self.ai_router = ai_router
589
+ self.ws = ws_manager
590
+
591
+ async def run_with_repair(
592
+ self,
593
+ command: str,
594
+ related_files: List[str] = [],
595
+ session_id: str = "",
596
+ task_id: str = "",
597
+ max_retries: int = 3,
598
+ ) -> Dict:
599
+ """Run command, auto-repair on failure, retry."""
600
+
601
+ attempt = 0
602
+ last_result = None
603
+
604
+ while attempt < max_retries:
605
+ attempt += 1
606
+
607
+ if self.ws and task_id and attempt > 1:
608
+ await self.ws.emit(task_id, "retry_attempt", {
609
+ "attempt": attempt,
610
+ "max_retries": max_retries,
611
+ "command": command[:80],
612
+ }, session_id=session_id)
613
+
614
+ result = await self.terminal.execute(command, session_id=session_id, task_id=task_id)
615
+ last_result = result
616
+
617
+ if result["success"]:
618
+ if attempt > 1:
619
+ if self.ws and task_id:
620
+ await self.ws.emit(task_id, "self_heal_success", {
621
+ "attempts": attempt,
622
+ "command": command[:80],
623
+ }, session_id=session_id)
624
+ return {**result, "attempts": attempt, "self_healed": attempt > 1}
625
+
626
+ # Failed β€” analyze error and attempt repair
627
+ if self.ws and task_id:
628
+ await self.ws.emit(task_id, "self_heal_attempt", {
629
+ "attempt": attempt,
630
+ "error_snippet": result["output"][-500:],
631
+ "command": command[:80],
632
+ }, session_id=session_id)
633
+
634
+ if not self.ai_router:
635
+ break
636
+
637
+ # Ask AI to analyze error and suggest fix
638
+ patch = await self._analyze_and_repair(
639
+ command=command,
640
+ error_output=result["output"],
641
+ related_files=related_files,
642
+ session_id=session_id,
643
+ task_id=task_id,
644
+ )
645
+
646
+ if patch and patch.get("fixed"):
647
+ # Apply patches to files
648
+ for file_patch in patch.get("file_patches", []):
649
+ fpath = file_patch.get("file", "")
650
+ old = file_patch.get("old", "")
651
+ new = file_patch.get("new", "")
652
+ if fpath and old and new:
653
+ await self.filesystem.patch_file(fpath, old, new, session_id=session_id, task_id=task_id)
654
+
655
+ # Update command if needed
656
+ if patch.get("new_command"):
657
+ command = patch["new_command"]
658
+
659
+ # Install missing packages if needed
660
+ if patch.get("install_command"):
661
+ await self.terminal.execute(patch["install_command"], session_id=session_id, task_id=task_id)
662
+
663
+ await asyncio.sleep(1)
664
+
665
+ # All retries exhausted
666
+ if self.ws and task_id:
667
+ await self.ws.emit(task_id, "self_heal_failed", {
668
+ "attempts": attempt,
669
+ "final_error": (last_result or {}).get("output", "")[-300:],
670
+ }, session_id=session_id)
671
+
672
+ return {**(last_result or {}), "attempts": attempt, "self_healed": False}
673
+
674
+ async def _analyze_and_repair(
675
+ self,
676
+ command: str,
677
+ error_output: str,
678
+ related_files: List[str],
679
+ session_id: str,
680
+ task_id: str,
681
+ ) -> Dict:
682
+ """Use AI to analyze error and produce repair instructions."""
683
+
684
+ # Read related file contents for context
685
+ file_context = ""
686
+ for fp in related_files[:3]:
687
+ try:
688
+ full_path = os.path.join(WORKSPACE, fp)
689
+ if os.path.exists(full_path):
690
+ with open(full_path, "r", errors="replace") as f:
691
+ content = f.read()[:1000]
692
+ file_context += f"\n\nFile: {fp}\n```\n{content}\n```"
693
+ except Exception:
694
+ pass
695
+
696
+ messages = [
697
+ {
698
+ "role": "system",
699
+ "content": (
700
+ "You are an expert debugger. Analyze the error and provide a precise repair.\n"
701
+ "Respond ONLY with valid JSON.\n"
702
+ "Format:\n"
703
+ '{"fixed": true/false, '
704
+ '"analysis": "brief error explanation", '
705
+ '"fix_description": "what to fix", '
706
+ '"install_command": "pip install X or npm install X if missing package, else null", '
707
+ '"new_command": "corrected command if needed, else null", '
708
+ '"file_patches": [{"file": "filename", "old": "exact old text", "new": "exact new text"}]}'
709
+ ),
710
+ },
711
+ {
712
+ "role": "user",
713
+ "content": (
714
+ f"Command that failed:\n```\n{command}\n```\n\n"
715
+ f"Error output:\n```\n{error_output[-2000:]}\n```"
716
+ f"{file_context}"
717
+ ),
718
+ },
719
+ ]
720
+
721
+ try:
722
+ raw = await self.ai_router.complete(messages, temperature=0.1, max_tokens=1000)
723
+ start = raw.find("{")
724
+ end = raw.rfind("}") + 1
725
+ if start >= 0 and end > start:
726
+ return json.loads(raw[start:end])
727
+ except Exception as e:
728
+ log.warning("Self-repair analysis failed", error=str(e))
729
+
730
+ return {"fixed": False}
731
+
732
+
733
+ # ═══════════════════════════════════════════════════════════════════
734
+ # DEPLOYMENT ENGINE
735
+ # ═══════════════════════════════════════════════════════════════════
736
+
737
+ class DeploymentEngine:
738
+ """Deployment automation for Vercel + HuggingFace."""
739
+
740
+ def __init__(self, ws_manager=None):
741
+ self.ws = ws_manager
742
+ self.vercel_token = VERCEL_TOKEN
743
+ self.hf_token = HF_TOKEN
744
+
745
+ async def deploy_vercel(
746
+ self,
747
+ project_dir: str,
748
+ project_name: str,
749
+ session_id: str = "",
750
+ task_id: str = "",
751
+ ) -> Dict:
752
+ """Deploy a project to Vercel."""
753
+ if self.ws and task_id:
754
+ await self.ws.emit(task_id, "deploy_start", {
755
+ "platform": "vercel",
756
+ "project": project_name,
757
+ }, session_id=session_id)
758
+
759
+ # Use vercel CLI if available
760
+ env = {**os.environ, "VERCEL_TOKEN": self.vercel_token}
761
+ try:
762
+ proc = await asyncio.create_subprocess_shell(
763
+ f"cd {project_dir} && npx vercel --token={self.vercel_token} --yes --prod 2>&1",
764
+ stdout=asyncio.subprocess.PIPE,
765
+ stderr=asyncio.subprocess.STDOUT,
766
+ env=env,
767
+ )
768
+ stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=300)
769
+ output = stdout.decode("utf-8", errors="replace")
770
+
771
+ # Extract URL from output
772
+ url_match = re.search(r"https://[a-zA-Z0-9\-]+\.vercel\.app", output)
773
+ url = url_match.group(0) if url_match else ""
774
+
775
+ success = proc.returncode == 0 and bool(url)
776
+
777
+ if self.ws and task_id:
778
+ await self.ws.emit(task_id, "deploy_complete", {
779
+ "platform": "vercel",
780
+ "success": success,
781
+ "url": url,
782
+ "project": project_name,
783
+ }, session_id=session_id)
784
+
785
+ return {"success": success, "url": url, "output": output[-1000:], "platform": "vercel"}
786
+
787
+ except Exception as e:
788
+ return {"success": False, "error": str(e), "platform": "vercel"}
789
+
790
+ async def deploy_hf_space(
791
+ self,
792
+ repo_path: str,
793
+ space_name: str,
794
+ space_type: str = "gradio",
795
+ session_id: str = "",
796
+ task_id: str = "",
797
+ ) -> Dict:
798
+ """Push to HuggingFace Space."""
799
+ if self.ws and task_id:
800
+ await self.ws.emit(task_id, "deploy_start", {
801
+ "platform": "huggingface",
802
+ "space": space_name,
803
+ }, session_id=session_id)
804
+
805
+ hf_url = f"https://huggingface.co/spaces/{space_name}"
806
+ git_url = f"https://huggingface.co/spaces/{space_name}.git"
807
+
808
+ # Inject HF token
809
+ auth_url = git_url.replace("https://", f"https://user:{self.hf_token}@")
810
+
811
+ try:
812
+ cmds = [
813
+ f"cd {repo_path} && git remote remove hf_space 2>/dev/null; git remote add hf_space {auth_url}",
814
+ f"cd {repo_path} && git push hf_space main --force 2>&1",
815
+ ]
816
+ results = []
817
+ for cmd in cmds:
818
+ proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
819
+ stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=120)
820
+ results.append({"cmd": cmd[:40], "success": proc.returncode == 0, "out": stdout.decode("utf-8", errors="replace")[:300]})
821
+
822
+ success = results[-1]["success"]
823
+ if self.ws and task_id:
824
+ await self.ws.emit(task_id, "deploy_complete", {
825
+ "platform": "huggingface",
826
+ "success": success,
827
+ "url": hf_url,
828
+ "space": space_name,
829
+ }, session_id=session_id)
830
+
831
+ return {"success": success, "url": hf_url, "platform": "huggingface", "steps": results}
832
+
833
+ except Exception as e:
834
+ return {"success": False, "error": str(e), "platform": "huggingface"}
835
+
836
+
837
+ # ═══════════════════════════════════════════════════════════════════
838
+ # UNIFIED REAL TOOL ROUTER
839
+ # ═══════════════════════════════════════════════════════════════════
840
+
841
+ class RealToolRouter:
842
+ """
843
+ Routes tool calls to real implementations.
844
+ This is the core of the autonomous execution system.
845
+ """
846
+
847
+ def __init__(self, ws_manager=None, ai_router=None):
848
+ self.ws = ws_manager
849
+ self.ai_router = ai_router
850
+ self.terminal = TerminalEngine(ws_manager=ws_manager)
851
+ self.filesystem = FilesystemEngine(ws_manager=ws_manager)
852
+ self.github = GitHubEngine(ws_manager=ws_manager)
853
+ self.repair = SelfRepairEngine(
854
+ terminal=self.terminal,
855
+ filesystem=self.filesystem,
856
+ ai_router=ai_router,
857
+ ws_manager=ws_manager,
858
+ )
859
+ self.deployment = DeploymentEngine(ws_manager=ws_manager)
860
+
861
+ async def route(
862
+ self,
863
+ tool: str,
864
+ params: Dict,
865
+ session_id: str = "",
866
+ task_id: str = "",
867
+ ) -> Dict:
868
+ """Route a tool call to the correct engine."""
869
+
870
+ # Emit tool call event
871
+ if self.ws and task_id:
872
+ await self.ws.emit(task_id, "tool_called", {
873
+ "tool": tool,
874
+ "params_preview": str(params)[:100],
875
+ }, session_id=session_id)
876
+
877
+ handlers = {
878
+ # Terminal
879
+ "terminal.run": self._terminal_run,
880
+ "terminal.sequence": self._terminal_sequence,
881
+ "terminal.run_with_repair": self._terminal_repair,
882
+
883
+ # Filesystem
884
+ "fs.read": self._fs_read,
885
+ "fs.write": self._fs_write,
886
+ "fs.patch": self._fs_patch,
887
+ "fs.delete": self._fs_delete,
888
+ "fs.move": self._fs_move,
889
+ "fs.search": self._fs_search,
890
+ "fs.tree": self._fs_tree,
891
+ "fs.list": self._fs_list,
892
+
893
+ # GitHub
894
+ "github.clone": self._github_clone,
895
+ "github.create_repo": self._github_create_repo,
896
+ "github.commit_push": self._github_commit_push,
897
+ "github.create_branch": self._github_create_branch,
898
+ "github.create_pr": self._github_create_pr,
899
+ "github.read_issues": self._github_issues,
900
+
901
+ # Deployment
902
+ "deploy.vercel": self._deploy_vercel,
903
+ "deploy.hf": self._deploy_hf,
904
+
905
+ # Meta
906
+ "workspace.info": self._workspace_info,
907
+ }
908
+
909
+ handler = handlers.get(tool)
910
+ if not handler:
911
+ return {"success": False, "error": f"Unknown tool: {tool}", "available": list(handlers.keys())}
912
+
913
+ try:
914
+ result = await handler(params, session_id=session_id, task_id=task_id)
915
+
916
+ # Emit result event
917
+ if self.ws and task_id:
918
+ await self.ws.emit(task_id, "tool_result", {
919
+ "tool": tool,
920
+ "success": result.get("success", True),
921
+ "summary": str(result)[:150],
922
+ }, session_id=session_id)
923
+
924
+ return result
925
+ except Exception as e:
926
+ log.error("Tool execution failed", tool=tool, error=str(e))
927
+ return {"success": False, "error": str(e), "tool": tool}
928
+
929
+ # ─── Terminal Handlers ────────────────────────────────────────────────────
930
+ async def _terminal_run(self, params: Dict, session_id: str, task_id: str) -> Dict:
931
+ return await self.terminal.execute(
932
+ params.get("command", ""),
933
+ session_id=session_id,
934
+ task_id=task_id,
935
+ cwd=params.get("cwd"),
936
+ timeout=params.get("timeout", 60),
937
+ )
938
+
939
+ async def _terminal_sequence(self, params: Dict, session_id: str, task_id: str) -> Dict:
940
+ results = await self.terminal.run_sequence(
941
+ params.get("commands", []),
942
+ session_id=session_id,
943
+ task_id=task_id,
944
+ stop_on_error=params.get("stop_on_error", True),
945
+ )
946
+ return {"success": all(r["success"] for r in results), "steps": results}
947
+
948
+ async def _terminal_repair(self, params: Dict, session_id: str, task_id: str) -> Dict:
949
+ return await self.repair.run_with_repair(
950
+ params.get("command", ""),
951
+ related_files=params.get("related_files", []),
952
+ session_id=session_id,
953
+ task_id=task_id,
954
+ max_retries=params.get("max_retries", 3),
955
+ )
956
+
957
+ # ─── Filesystem Handlers ──────────────────────────────────────────────────
958
+ async def _fs_read(self, params: Dict, session_id: str, task_id: str) -> Dict:
959
+ return await self.filesystem.read_file(params.get("path", ""), session_id=session_id, task_id=task_id)
960
+
961
+ async def _fs_write(self, params: Dict, session_id: str, task_id: str) -> Dict:
962
+ return await self.filesystem.write_file(params.get("path", ""), params.get("content", ""), session_id=session_id, task_id=task_id)
963
+
964
+ async def _fs_patch(self, params: Dict, session_id: str, task_id: str) -> Dict:
965
+ return await self.filesystem.patch_file(params.get("path", ""), params.get("old", ""), params.get("new", ""), session_id=session_id, task_id=task_id)
966
+
967
+ async def _fs_delete(self, params: Dict, session_id: str, task_id: str) -> Dict:
968
+ return await self.filesystem.delete_file(params.get("path", ""), session_id=session_id, task_id=task_id)
969
+
970
+ async def _fs_move(self, params: Dict, session_id: str, task_id: str) -> Dict:
971
+ return await self.filesystem.move_file(params.get("src", ""), params.get("dst", ""), session_id=session_id, task_id=task_id)
972
+
973
+ async def _fs_search(self, params: Dict, session_id: str, task_id: str) -> Dict:
974
+ return await self.filesystem.search_files(params.get("query", ""), params.get("path", ""), params.get("pattern", "*"))
975
+
976
+ async def _fs_tree(self, params: Dict, session_id: str, task_id: str) -> Dict:
977
+ return await self.filesystem.tree(params.get("path", ""), params.get("max_depth", 4))
978
+
979
+ async def _fs_list(self, params: Dict, session_id: str, task_id: str) -> Dict:
980
+ return await self.filesystem.list_dir(params.get("path", ""))
981
+
982
+ # ─── GitHub Handlers ─────────────────────────────────────────────────────���
983
+ async def _github_clone(self, params: Dict, session_id: str, task_id: str) -> Dict:
984
+ return await self.github.clone_repo(params.get("url", ""), params.get("target", ""), session_id=session_id, task_id=task_id)
985
+
986
+ async def _github_create_repo(self, params: Dict, session_id: str, task_id: str) -> Dict:
987
+ return await self.github.create_repo(params.get("name", ""), params.get("description", ""), params.get("private", False), session_id=session_id, task_id=task_id)
988
+
989
+ async def _github_commit_push(self, params: Dict, session_id: str, task_id: str) -> Dict:
990
+ return await self.github.commit_and_push(params.get("repo_path", WORKSPACE), params.get("message", "God Agent commit"), params.get("branch", "main"), session_id=session_id, task_id=task_id)
991
+
992
+ async def _github_create_branch(self, params: Dict, session_id: str, task_id: str) -> Dict:
993
+ return await self.github.create_branch(params.get("repo_path", WORKSPACE), params.get("branch", ""), session_id=session_id, task_id=task_id)
994
+
995
+ async def _github_create_pr(self, params: Dict, session_id: str, task_id: str) -> Dict:
996
+ return await self.github.create_pr(params.get("owner", ""), params.get("repo", ""), params.get("title", ""), params.get("body", ""), params.get("head", ""), session_id=session_id, task_id=task_id)
997
+
998
+ async def _github_issues(self, params: Dict, session_id: str, task_id: str) -> Dict:
999
+ issues = await self.github.read_issues(params.get("owner", ""), params.get("repo", ""))
1000
+ return {"success": True, "issues": issues}
1001
+
1002
+ # ─── Deployment Handlers ──────────────────────────────────────────────────
1003
+ async def _deploy_vercel(self, params: Dict, session_id: str, task_id: str) -> Dict:
1004
+ return await self.deployment.deploy_vercel(params.get("dir", WORKSPACE), params.get("name", "god-agent-app"), session_id=session_id, task_id=task_id)
1005
+
1006
+ async def _deploy_hf(self, params: Dict, session_id: str, task_id: str) -> Dict:
1007
+ return await self.deployment.deploy_hf_space(params.get("repo_path", WORKSPACE), params.get("space_name", ""), session_id=session_id, task_id=task_id)
1008
+
1009
+ # ─── Meta ─────────────────────────────────────────────────────────────────
1010
+ async def _workspace_info(self, params: Dict, session_id: str, task_id: str) -> Dict:
1011
+ tree = await self.filesystem.tree()
1012
+ return {
1013
+ "success": True,
1014
+ "workspace": WORKSPACE,
1015
+ "files": tree.get("files", []),
1016
+ "file_count": tree.get("count", 0),
1017
+ }
1018
+
1019
+
1020
+ # ─── Singleton instance ────────────────────────────────────────────────────────
1021
+ _tool_router: Optional[RealToolRouter] = None
1022
+
1023
+ def get_tool_router(ws_manager=None, ai_router=None) -> RealToolRouter:
1024
+ global _tool_router
1025
+ if _tool_router is None:
1026
+ _tool_router = RealToolRouter(ws_manager=ws_manager, ai_router=ai_router)
1027
+ return _tool_router