nucleus-mcp 1.0.7 → 1.2.0
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.
- package/package.json +2 -4
- package/.github/workflows/ci.yml +0 -72
- package/.smithery/shttp/module.js +0 -3
- package/.smithery/shttp/module.js.map +0 -7
- package/.smithery/stdio/index.cjs +0 -74
- package/.smithery/stdio/index.cjs.map +0 -7
- package/CHANGELOG.md +0 -65
- package/CODE_OF_CONDUCT.md +0 -34
- package/COMPARISON.md +0 -20
- package/CONTRIBUTING.md +0 -137
- package/DEVELOPMENT.md +0 -109
- package/LICENSE +0 -21
- package/README.md +0 -319
- package/ROADMAP.md +0 -24
- package/SECURITY.md +0 -88
- package/docs/COMMUNITY_FAQ.md +0 -48
- package/docs/COMPETITIVE_ANALYSIS.md +0 -90
- package/docs/DEMO_SCRIPT.md +0 -86
- package/docs/ENTERPRISE.md +0 -169
- package/docs/FAQ.md +0 -228
- package/docs/QUICK_START.md +0 -155
- package/docs/README.md +0 -67
- package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
- package/docs/social-preview-hq.png +0 -0
- package/docs/social-preview.png +0 -0
- package/examples/basic_usage.py +0 -84
- package/package.json.real +0 -24
- package/pyproject.toml +0 -50
- package/registry-server.json +0 -20
- package/scripts/demo_governance.py +0 -109
- package/scripts/sse_bridge.py +0 -87
- package/scripts/verify_windows_paths.py +0 -48
- package/server.json +0 -20
- package/src/mcp_server_nucleus/__init__.py +0 -8583
- package/src/mcp_server_nucleus/__main__.py +0 -9
- package/src/mcp_server_nucleus/cli.py +0 -1225
- package/src/mcp_server_nucleus/commitment_core.py +0 -146
- package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
- package/src/mcp_server_nucleus/core/__init__.py +0 -6
- package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
- package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
- package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
- package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
- package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
- package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
- package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
- package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
- package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
- package/src/mcp_server_nucleus/runtime/agent.py +0 -517
- package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
- package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
- package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
- package/src/mcp_server_nucleus/runtime/auth.py +0 -98
- package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
- package/src/mcp_server_nucleus/runtime/broker.py +0 -126
- package/src/mcp_server_nucleus/runtime/budget.py +0 -153
- package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
- package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
- package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
- package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
- package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
- package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
- package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
- package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
- package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
- package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
- package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
- package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
- package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
- package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
- package/src/mcp_server_nucleus/runtime/common.py +0 -134
- package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
- package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
- package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
- package/src/mcp_server_nucleus/runtime/critic.py +0 -150
- package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
- package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
- package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
- package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
- package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
- package/src/mcp_server_nucleus/runtime/export.py +0 -64
- package/src/mcp_server_nucleus/runtime/factory.py +0 -594
- package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
- package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
- package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
- package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
- package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
- package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
- package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
- package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
- package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
- package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
- package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
- package/src/mcp_server_nucleus/runtime/installer.py +0 -81
- package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
- package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
- package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
- package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
- package/src/mcp_server_nucleus/runtime/locking.py +0 -228
- package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
- package/src/mcp_server_nucleus/runtime/memory.py +0 -158
- package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
- package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
- package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
- package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
- package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
- package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
- package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
- package/src/mcp_server_nucleus/runtime/policy.py +0 -117
- package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
- package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
- package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
- package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
- package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
- package/src/mcp_server_nucleus/runtime/registry.py +0 -82
- package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
- package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
- package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
- package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
- package/src/mcp_server_nucleus/runtime/storage.py +0 -83
- package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
- package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
- package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
- package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
- package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
- package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
- package/src/mcp_server_nucleus/runtime/team.py +0 -59
- package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
- package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
- package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
- package/src/mcp_server_nucleus/tool_tiers.py +0 -243
- package/tests/__init__.py +0 -1
- package/tests/conftest.py +0 -42
- package/tests/test_core.py +0 -235
- package/tests/test_search.py +0 -69
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict, Any, Optional
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
import asyncio
|
|
5
|
-
import logging
|
|
6
|
-
import urllib.request
|
|
7
|
-
import urllib.error
|
|
8
|
-
import json
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from .base import Capability
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger("nucleus.render_poller")
|
|
13
|
-
|
|
14
|
-
# Global dict to store active polls in memory
|
|
15
|
-
# {service_id: {"commit_sha": str, "start_time": float, "status": str, "task": asyncio.Task}}
|
|
16
|
-
ACTIVE_POLLS = {}
|
|
17
|
-
|
|
18
|
-
class RenderPolling(Capability):
|
|
19
|
-
def __init__(self):
|
|
20
|
-
self._brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", "/Users/lokeshgarg/.gemini/antigravity/brain/7c654df4-b83e-43f9-8620-f15868ec39d1"))
|
|
21
|
-
self._api_key = os.environ.get("RENDER_API_KEY")
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def name(self) -> str:
|
|
25
|
-
return "render_poller"
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def description(self) -> str:
|
|
29
|
-
return "Automated deployment monitoring for Render services."
|
|
30
|
-
|
|
31
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
32
|
-
return [
|
|
33
|
-
{
|
|
34
|
-
"name": "brain_start_deploy_poll",
|
|
35
|
-
"description": "Start monitoring a Render deploy in the background.",
|
|
36
|
-
"parameters": {
|
|
37
|
-
"type": "object",
|
|
38
|
-
"properties": {
|
|
39
|
-
"service_id": {"type": "string", "description": "Render service ID (e.g., 'srv-abc123')"},
|
|
40
|
-
"commit_sha": {"type": "string", "description": "Optional Git commit SHA being deployed"}
|
|
41
|
-
},
|
|
42
|
-
"required": ["service_id"]
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"name": "brain_check_deploy",
|
|
47
|
-
"description": "Check status of an active deploy poll.",
|
|
48
|
-
"parameters": {
|
|
49
|
-
"type": "object",
|
|
50
|
-
"properties": {
|
|
51
|
-
"service_id": {"type": "string", "description": "Render service ID to check"}
|
|
52
|
-
},
|
|
53
|
-
"required": ["service_id"]
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"name": "brain_smoke_test",
|
|
58
|
-
"description": "Run a smoke test on any URL.",
|
|
59
|
-
"parameters": {
|
|
60
|
-
"type": "object",
|
|
61
|
-
"properties": {
|
|
62
|
-
"url": {"type": "string", "description": "Base URL of service (e.g., 'https://myapp.onrender.com')"},
|
|
63
|
-
"endpoint": {"type": "string", "description": "Health endpoint to hit (default: '/api/health')"}
|
|
64
|
-
},
|
|
65
|
-
"required": ["url"]
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
def execute_tool(self, tool_name: str, args: Dict) -> Any:
|
|
71
|
-
try:
|
|
72
|
-
if tool_name == "brain_start_deploy_poll":
|
|
73
|
-
return self._start_poll(args.get("service_id"), args.get("commit_sha"))
|
|
74
|
-
elif tool_name == "brain_check_deploy":
|
|
75
|
-
return self._check_poll(args.get("service_id"))
|
|
76
|
-
elif tool_name == "brain_smoke_test":
|
|
77
|
-
return self._smoke_test(args.get("url"), args.get("endpoint", "/api/health"))
|
|
78
|
-
return f"Tool {tool_name} not found"
|
|
79
|
-
except Exception as e:
|
|
80
|
-
return f"Error executing {tool_name}: {str(e)}"
|
|
81
|
-
|
|
82
|
-
def _start_poll(self, service_id: str, commit_sha: Optional[str] = None) -> Dict:
|
|
83
|
-
"""Start a background polling task."""
|
|
84
|
-
if not service_id:
|
|
85
|
-
return {"error": "service_id required"}
|
|
86
|
-
|
|
87
|
-
# Cancel existing poll for this service
|
|
88
|
-
if service_id in ACTIVE_POLLS:
|
|
89
|
-
existing = ACTIVE_POLLS[service_id]
|
|
90
|
-
if not existing["task"].done():
|
|
91
|
-
existing["task"].cancel()
|
|
92
|
-
logger.info(f"Cancelled existing poll for {service_id}")
|
|
93
|
-
|
|
94
|
-
# Start new task
|
|
95
|
-
task = asyncio.create_task(self._poll_loop(service_id, commit_sha))
|
|
96
|
-
|
|
97
|
-
ACTIVE_POLLS[service_id] = {
|
|
98
|
-
"commit_sha": commit_sha or "latest",
|
|
99
|
-
"start_time": time.time(),
|
|
100
|
-
"status": "polling",
|
|
101
|
-
"task": task,
|
|
102
|
-
"logs": []
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
# Log event
|
|
106
|
-
try:
|
|
107
|
-
from ... import _emit_event
|
|
108
|
-
_emit_event("deploy_poll_started", "render_poller", {
|
|
109
|
-
"service_id": service_id,
|
|
110
|
-
"commit_sha": commit_sha
|
|
111
|
-
})
|
|
112
|
-
except ImportError:
|
|
113
|
-
pass # Circular import or not available
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
"success": True,
|
|
117
|
-
"message": f"Started polling {service_id}",
|
|
118
|
-
"poll_id": f"poll_{service_id}_{int(time.time())}"
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
def _check_poll(self, service_id: str) -> Dict:
|
|
122
|
-
"""Check status of an active poll."""
|
|
123
|
-
if service_id not in ACTIVE_POLLS:
|
|
124
|
-
return {"status": "not_found", "message": "No active poll for this service."}
|
|
125
|
-
|
|
126
|
-
poll = ACTIVE_POLLS[service_id]
|
|
127
|
-
uptime = int(time.time() - poll["start_time"])
|
|
128
|
-
|
|
129
|
-
# Check if task is done (failed or finished)
|
|
130
|
-
if poll["task"].done():
|
|
131
|
-
try:
|
|
132
|
-
result = poll["task"].result()
|
|
133
|
-
return {
|
|
134
|
-
"status": "complete",
|
|
135
|
-
"result": result,
|
|
136
|
-
"uptime_seconds": uptime
|
|
137
|
-
}
|
|
138
|
-
except asyncio.CancelledError:
|
|
139
|
-
return {"status": "cancelled", "uptime_seconds": uptime}
|
|
140
|
-
except Exception as e:
|
|
141
|
-
return {"status": "error", "error": str(e), "uptime_seconds": uptime}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
"status": "polling",
|
|
145
|
-
"uptime_seconds": uptime,
|
|
146
|
-
"commit_sha": poll["commit_sha"]
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async def _poll_loop(self, service_id: str, commit_sha: Optional[str]):
|
|
150
|
-
"""Async loop to check Render API."""
|
|
151
|
-
timeout_mins = 20
|
|
152
|
-
start_time = time.time()
|
|
153
|
-
|
|
154
|
-
if not self._api_key:
|
|
155
|
-
# Simulation mode if no key
|
|
156
|
-
await asyncio.sleep(2)
|
|
157
|
-
if service_id == "srv-test":
|
|
158
|
-
await asyncio.sleep(1)
|
|
159
|
-
return {"success": True, "url": "https://test-app.onrender.com", "simulated": True}
|
|
160
|
-
|
|
161
|
-
raise ValueError("RENDER_API_KEY not set")
|
|
162
|
-
|
|
163
|
-
headers = {"Authorization": f"Bearer {self._api_key}"}
|
|
164
|
-
url = f"https://api.render.com/v1/services/{service_id}/deploys?limit=1"
|
|
165
|
-
req = urllib.request.Request(url, headers=headers)
|
|
166
|
-
|
|
167
|
-
while time.time() - start_time < (timeout_mins * 60):
|
|
168
|
-
try:
|
|
169
|
-
loop = asyncio.get_running_loop()
|
|
170
|
-
# Use run_in_executor for blocking urlopen
|
|
171
|
-
msg = await loop.run_in_executor(None, lambda: self._fetch_url(req))
|
|
172
|
-
|
|
173
|
-
if "error" in msg:
|
|
174
|
-
logger.error(f"Render API error: {msg['error']}")
|
|
175
|
-
else:
|
|
176
|
-
data = msg.get("json", [])
|
|
177
|
-
if data:
|
|
178
|
-
latest = data[0].get("deploy")
|
|
179
|
-
if latest:
|
|
180
|
-
status = latest.get("status")
|
|
181
|
-
# Match commit if provided
|
|
182
|
-
latest_commit = latest.get("commit", {}).get("id")
|
|
183
|
-
if commit_sha and latest_commit and not latest_commit.startswith(commit_sha):
|
|
184
|
-
# Not our commit yet
|
|
185
|
-
pass
|
|
186
|
-
elif status == "live":
|
|
187
|
-
deploy_url = f"https://{service_id}.onrender.com" # Simplification
|
|
188
|
-
|
|
189
|
-
# Run smoke test!
|
|
190
|
-
smoke = self._smoke_test(deploy_url)
|
|
191
|
-
|
|
192
|
-
# Log complete
|
|
193
|
-
try:
|
|
194
|
-
from ... import _emit_event
|
|
195
|
-
_emit_event("deploy_complete", "render_poller", {
|
|
196
|
-
"service_id": service_id,
|
|
197
|
-
"status": "live",
|
|
198
|
-
"url": deploy_url,
|
|
199
|
-
"smoke_test": smoke
|
|
200
|
-
})
|
|
201
|
-
except Exception:
|
|
202
|
-
pass
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
"success": True,
|
|
206
|
-
"url": deploy_url,
|
|
207
|
-
"smoke_test": smoke
|
|
208
|
-
}
|
|
209
|
-
elif status in ["build_failed", "update_failed", "canceled"]:
|
|
210
|
-
return {"success": False, "status": status}
|
|
211
|
-
|
|
212
|
-
except Exception as e:
|
|
213
|
-
logger.error(f"Poll error: {e}")
|
|
214
|
-
|
|
215
|
-
await asyncio.sleep(30)
|
|
216
|
-
|
|
217
|
-
return {"success": False, "error": "Timeout"}
|
|
218
|
-
|
|
219
|
-
def _fetch_url(self, req) -> Dict:
|
|
220
|
-
"""Helper to fetch URL synchronously."""
|
|
221
|
-
try:
|
|
222
|
-
with urllib.request.urlopen(req, timeout=60) as response:
|
|
223
|
-
if response.status == 200:
|
|
224
|
-
return {"json": json.load(response)}
|
|
225
|
-
return {"error": f"HTTP {response.status}"}
|
|
226
|
-
except Exception as e:
|
|
227
|
-
return {"error": str(e)}
|
|
228
|
-
|
|
229
|
-
def _smoke_test(self, url: str, endpoint: str = "/api/health") -> Dict:
|
|
230
|
-
"""Run a smoke test."""
|
|
231
|
-
try:
|
|
232
|
-
target = f"{url.rstrip('/')}{endpoint}"
|
|
233
|
-
start = time.time()
|
|
234
|
-
|
|
235
|
-
req = urllib.request.Request(target)
|
|
236
|
-
try:
|
|
237
|
-
with urllib.request.urlopen(req, timeout=60) as response:
|
|
238
|
-
latency = (time.time() - start) * 1000
|
|
239
|
-
passed = response.status == 200
|
|
240
|
-
# Check JSON content if valid
|
|
241
|
-
try:
|
|
242
|
-
data = json.load(response)
|
|
243
|
-
if isinstance(data, dict) and data.get("status") == "healthy":
|
|
244
|
-
passed = True
|
|
245
|
-
except Exception:
|
|
246
|
-
pass
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
"passed": passed,
|
|
250
|
-
"status_code": response.status,
|
|
251
|
-
"latency_ms": latency
|
|
252
|
-
}
|
|
253
|
-
except urllib.error.HTTPError as e:
|
|
254
|
-
return {
|
|
255
|
-
"passed": False,
|
|
256
|
-
"status_code": e.code,
|
|
257
|
-
"error": str(e)
|
|
258
|
-
}
|
|
259
|
-
except Exception as e:
|
|
260
|
-
return {
|
|
261
|
-
"passed": False,
|
|
262
|
-
"error": str(e)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
except Exception as e:
|
|
266
|
-
return {"passed": False, "error": str(e)}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import List, Dict, Any
|
|
3
|
-
import os
|
|
4
|
-
import subprocess
|
|
5
|
-
from .base import Capability
|
|
6
|
-
|
|
7
|
-
class SelfHealingOps(Capability):
|
|
8
|
-
def __init__(self):
|
|
9
|
-
# Default health command (can be overridden by env)
|
|
10
|
-
self.health_cmd = os.environ.get("NUCLEUS_HEALTH_CMD", "echo 'No health check configured.'")
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def name(self) -> str:
|
|
14
|
-
return "self_healing_ops"
|
|
15
|
-
|
|
16
|
-
@property
|
|
17
|
-
def description(self) -> str:
|
|
18
|
-
return "Diagnose and repair codebase issues."
|
|
19
|
-
|
|
20
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
21
|
-
return [
|
|
22
|
-
{
|
|
23
|
-
"name": "brain_scan_health",
|
|
24
|
-
"description": "Run the project's configured health check (tests/lints).",
|
|
25
|
-
"parameters": {
|
|
26
|
-
"type": "object",
|
|
27
|
-
"properties": {},
|
|
28
|
-
"required": []
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"name": "brain_generate_fix_plan",
|
|
33
|
-
"description": "Generate a markdown plan to fix a specific error.",
|
|
34
|
-
"parameters": {
|
|
35
|
-
"type": "object",
|
|
36
|
-
"properties": {
|
|
37
|
-
"error_log": {"type": "string", "description": "The error output or log to analyze"},
|
|
38
|
-
"context_files": {"type": "array", "items": {"type": "string"}, "description": "List of files related to the error"}
|
|
39
|
-
},
|
|
40
|
-
"required": ["error_log"]
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
def execute_tool(self, tool_name: str, args: Dict) -> Any:
|
|
46
|
-
if tool_name == "brain_scan_health":
|
|
47
|
-
return self._scan_health()
|
|
48
|
-
elif tool_name == "brain_generate_fix_plan":
|
|
49
|
-
return self._generate_fix_plan(args)
|
|
50
|
-
return f"Tool {tool_name} not found"
|
|
51
|
-
|
|
52
|
-
def _scan_health(self) -> Dict:
|
|
53
|
-
"""Runs the configured health check command."""
|
|
54
|
-
try:
|
|
55
|
-
# Security: We run the command as-is.
|
|
56
|
-
# In a real scenario, this should be sandboxed or strictly defined.
|
|
57
|
-
result = subprocess.run(
|
|
58
|
-
self.health_cmd,
|
|
59
|
-
shell=True,
|
|
60
|
-
capture_output=True,
|
|
61
|
-
text=True,
|
|
62
|
-
timeout=120 # 2 minute timeout for health checks
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
success = result.returncode == 0
|
|
66
|
-
return {
|
|
67
|
-
"success": success,
|
|
68
|
-
"output": result.stdout,
|
|
69
|
-
"errors": result.stderr,
|
|
70
|
-
"command": self.health_cmd
|
|
71
|
-
}
|
|
72
|
-
except subprocess.TimeoutExpired:
|
|
73
|
-
return {"success": False, "error": "Health check timed out."}
|
|
74
|
-
except Exception as e:
|
|
75
|
-
return {"success": False, "error": str(e)}
|
|
76
|
-
|
|
77
|
-
def _generate_fix_plan(self, args: Dict) -> str:
|
|
78
|
-
"""
|
|
79
|
-
Generates a structured plan for the user (or agent) to execute.
|
|
80
|
-
This DOES NOT Apply the fix. Safety first.
|
|
81
|
-
"""
|
|
82
|
-
error_log = args.get("error_log", "")
|
|
83
|
-
context_files = args.get("context_files", [])
|
|
84
|
-
|
|
85
|
-
# In a full recursive agent, we would use an LLM here to analyze the code.
|
|
86
|
-
# But since we ARE the LLM (when the tool returns), we return the prompt
|
|
87
|
-
# or a structure that helps the calling agent 'think'.
|
|
88
|
-
|
|
89
|
-
# Actually, if this tool is called by an Agent, the Agent expects a result.
|
|
90
|
-
# Returning a template helps the Agent formulate the next step.
|
|
91
|
-
|
|
92
|
-
return f"""# Fix Plan Generation
|
|
93
|
-
|
|
94
|
-
**Analyzed Error:**
|
|
95
|
-
```
|
|
96
|
-
{error_log[:500]}...
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**Context Files:**
|
|
100
|
-
{', '.join(context_files)}
|
|
101
|
-
|
|
102
|
-
**Recommended Strategy:**
|
|
103
|
-
1. Read the context files using `code_read_file`.
|
|
104
|
-
2. Analyze the logic causing the error.
|
|
105
|
-
3. Create a reproduction case if possible.
|
|
106
|
-
4. Apply the fix using `code_write_file`.
|
|
107
|
-
5. Re-run `brain_scan_health`.
|
|
108
|
-
|
|
109
|
-
*System Note: The Agent receiving this output should now proceed to Step 1.*
|
|
110
|
-
"""
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Strategy Capability
|
|
3
|
-
Allows the Oracle to interact with the "Cortex" (Strategy Folder).
|
|
4
|
-
"""
|
|
5
|
-
import json
|
|
6
|
-
import logging
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Dict, Any, List, Optional
|
|
9
|
-
from ..capabilities.base import Capability
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger("cap.strategy")
|
|
12
|
-
|
|
13
|
-
class StrategyTool(Capability):
|
|
14
|
-
def __init__(self, brain_path: Path, allowed_paths: List[str]):
|
|
15
|
-
self.brain_path = brain_path
|
|
16
|
-
# Resolve variables in paths
|
|
17
|
-
self.allowed_paths = [
|
|
18
|
-
Path(p.replace("${BRAIN_PATH}", str(brain_path))).resolve()
|
|
19
|
-
for p in allowed_paths
|
|
20
|
-
]
|
|
21
|
-
self._name = "strategy_ops"
|
|
22
|
-
self._desc = "Read and Evolve Strategic Protocols"
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def name(self): return self._name
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def description(self): return self._desc
|
|
29
|
-
|
|
30
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
31
|
-
return [
|
|
32
|
-
{
|
|
33
|
-
"name": "read_strategy",
|
|
34
|
-
"description": "Reads a strategy document or protocol.",
|
|
35
|
-
"parameters": {
|
|
36
|
-
"type": "object",
|
|
37
|
-
"properties": {
|
|
38
|
-
"filename": {"type": "string", "description": "Relative path to brain root (e.g. 'strategy/SOVEREIGN_TESTAMENT.md')"}
|
|
39
|
-
},
|
|
40
|
-
"required": ["filename"]
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"name": "evolve_protocol",
|
|
45
|
-
"description": "Overwrites/Updates a protocol document with new evolved content.",
|
|
46
|
-
"parameters": {
|
|
47
|
-
"type": "object",
|
|
48
|
-
"properties": {
|
|
49
|
-
"filename": {"type": "string"},
|
|
50
|
-
"content": {"type": "string", "description": "The new content"},
|
|
51
|
-
"reason": {"type": "string", "description": "Why was this evolution necessary?"}
|
|
52
|
-
},
|
|
53
|
-
"required": ["filename", "content", "reason"]
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
def _validate_path(self, filename: str) -> Optional[Path]:
|
|
59
|
-
# Simple security verify
|
|
60
|
-
target = (self.brain_path / filename).resolve()
|
|
61
|
-
|
|
62
|
-
# Check if target is within any allowed path
|
|
63
|
-
allowed = False
|
|
64
|
-
for parent in self.allowed_paths:
|
|
65
|
-
# Check exact match (File allow)
|
|
66
|
-
if target == parent:
|
|
67
|
-
allowed = True
|
|
68
|
-
break
|
|
69
|
-
# Check directory containment
|
|
70
|
-
# We assume if it doesn't have an suffix, it might be a dir,
|
|
71
|
-
# or we just check if it IS a dir on disk.
|
|
72
|
-
# If it's a non-existent dir whitelisted, we can't easily distinguish from file without trailing slash convention.
|
|
73
|
-
# For now, rely on standard pathlib behavior.
|
|
74
|
-
try:
|
|
75
|
-
if parent.is_dir() and str(target).startswith(str(parent)):
|
|
76
|
-
allowed = True
|
|
77
|
-
break
|
|
78
|
-
except OSError:
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
if not allowed:
|
|
82
|
-
logger.warning(f"⛔ ACCESS DENIED: {target} is not in allowed strategy paths.")
|
|
83
|
-
return None
|
|
84
|
-
|
|
85
|
-
return target
|
|
86
|
-
|
|
87
|
-
def execute(self, params: Dict[str, Any]) -> str:
|
|
88
|
-
# Since I'm binding multiple tools to one capability execute,
|
|
89
|
-
# I need a way to dispatch.
|
|
90
|
-
# But Capability.execute usually takes 'tool_name' or we rely on the implementation wrapping it.
|
|
91
|
-
# Wait, the Standard we mostly used was:
|
|
92
|
-
# 'execute' takes the params of the *tool call*.
|
|
93
|
-
# But which tool?
|
|
94
|
-
# Standard implementation in 'tool_manager' usually passes the Tool Name AND arguments.
|
|
95
|
-
# But 'Capability.execute' interface in this codebase usually takes just params?
|
|
96
|
-
# Let's check 'base.py' or 'filesystem.py'.
|
|
97
|
-
# Assuming for now we need a 'name' or 'action' in params, or we implement separate capabilities.
|
|
98
|
-
# Let's assume the caller passes the tool name or strict params.
|
|
99
|
-
|
|
100
|
-
# Hack for MVP: Check keys
|
|
101
|
-
filename = params.get("filename")
|
|
102
|
-
content = params.get("content")
|
|
103
|
-
reason = params.get("reason")
|
|
104
|
-
|
|
105
|
-
if content is None:
|
|
106
|
-
# READ
|
|
107
|
-
path = self._validate_path(filename)
|
|
108
|
-
if not path:
|
|
109
|
-
return "Error: Access Denied"
|
|
110
|
-
if not path.exists():
|
|
111
|
-
return "Error: File not found"
|
|
112
|
-
return path.read_text()
|
|
113
|
-
else:
|
|
114
|
-
# WRITE (Evolve)
|
|
115
|
-
path = self._validate_path(filename)
|
|
116
|
-
if not path:
|
|
117
|
-
return "Error: Access Denied"
|
|
118
|
-
|
|
119
|
-
# Log the evolution
|
|
120
|
-
log_path = self.brain_path / "decisions" / "evolution_log.jsonl"
|
|
121
|
-
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
-
|
|
123
|
-
import datetime
|
|
124
|
-
entry = {
|
|
125
|
-
"timestamp": datetime.datetime.now().isoformat(),
|
|
126
|
-
"file": filename,
|
|
127
|
-
"reason": reason,
|
|
128
|
-
"len_delta": len(content) - (path.stat().st_size if path.exists() else 0)
|
|
129
|
-
}
|
|
130
|
-
with open(log_path, "a") as f:
|
|
131
|
-
f.write(json.dumps(entry) + "\n")
|
|
132
|
-
|
|
133
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
134
|
-
path.write_text(content)
|
|
135
|
-
return f"Protocol {filename} evolved. Reason: {reason}"
|
|
136
|
-
|
|
137
|
-
def get_capability(brain_path: Path, config: Dict):
|
|
138
|
-
return StrategyTool(brain_path, config.get("paths", []))
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Synthesizer
|
|
3
|
-
===================
|
|
4
|
-
The 'Executive Brain' that synthesizes system status into human-readable reports.
|
|
5
|
-
|
|
6
|
-
Capabilities:
|
|
7
|
-
- brain_synthesize_status_report: Generates a 'State of the Union' roadmap.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Dict, Optional
|
|
13
|
-
import logging
|
|
14
|
-
import subprocess
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
from google import genai
|
|
18
|
-
HAS_GENAI = True
|
|
19
|
-
except ImportError:
|
|
20
|
-
HAS_GENAI = False
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
def _get_api_key() -> Optional[str]:
|
|
25
|
-
return os.environ.get("GEMINI_API_KEY")
|
|
26
|
-
|
|
27
|
-
def brain_synthesize_status_report(
|
|
28
|
-
project_root: str,
|
|
29
|
-
focus: str = "roadmap"
|
|
30
|
-
) -> Dict[str, str]:
|
|
31
|
-
"""
|
|
32
|
-
Generates a 'State of the Union' report by analyzing tasks, logs, and vision.
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
project_root: Absolute path to project root
|
|
36
|
-
focus: 'roadmap' (default) or 'technical' or 'marketing'
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
Dict with status and output report content
|
|
40
|
-
"""
|
|
41
|
-
if not HAS_GENAI:
|
|
42
|
-
return {"status": "error", "message": "google-genai library not installed"}
|
|
43
|
-
|
|
44
|
-
api_key = _get_api_key()
|
|
45
|
-
if not api_key:
|
|
46
|
-
return {"status": "error", "message": "GEMINI_API_KEY not found"}
|
|
47
|
-
|
|
48
|
-
root_path = Path(project_root)
|
|
49
|
-
|
|
50
|
-
# 1. Gather Context
|
|
51
|
-
context = {}
|
|
52
|
-
|
|
53
|
-
# Task List (What's pending)
|
|
54
|
-
task_path = root_path / ".brain" / "task.md"
|
|
55
|
-
if task_path.exists():
|
|
56
|
-
with open(task_path, 'r') as f:
|
|
57
|
-
context['tasks'] = f.read()[:5000] # Cap usage
|
|
58
|
-
|
|
59
|
-
# Vision (Where we are going)
|
|
60
|
-
vision_path = root_path / ".brain" / "NUCLEUS_VISION.md"
|
|
61
|
-
if vision_path.exists():
|
|
62
|
-
with open(vision_path, 'r') as f:
|
|
63
|
-
context['vision'] = f.read()
|
|
64
|
-
|
|
65
|
-
# System Status (invisible work)
|
|
66
|
-
try:
|
|
67
|
-
cron_output = subprocess.check_output(['crontab', '-l'], stderr=subprocess.STDOUT).decode('utf-8')
|
|
68
|
-
context['cron'] = cron_output
|
|
69
|
-
except Exception:
|
|
70
|
-
context['cron'] = "No crontab accessible."
|
|
71
|
-
|
|
72
|
-
# Logs (Last 20 lines of key logs)
|
|
73
|
-
log_paths = [
|
|
74
|
-
root_path / ".brain/ledger/cron.log",
|
|
75
|
-
Path("/tmp/nucleus_nightly.log"),
|
|
76
|
-
Path("/tmp/nucleus_orchestrator.log")
|
|
77
|
-
]
|
|
78
|
-
logs_summary = ""
|
|
79
|
-
for log_p in log_paths:
|
|
80
|
-
if log_p.exists():
|
|
81
|
-
try:
|
|
82
|
-
tail = subprocess.check_output(['tail', '-n', '10', str(log_p)]).decode('utf-8')
|
|
83
|
-
logs_summary += f"\n--- {log_p.name} ---\n{tail}\n"
|
|
84
|
-
except Exception:
|
|
85
|
-
pass
|
|
86
|
-
context['logs'] = logs_summary
|
|
87
|
-
|
|
88
|
-
# 2. Construct Prompt
|
|
89
|
-
prompt = f"""
|
|
90
|
-
You are the Nucleus Brain (Executive Function).
|
|
91
|
-
|
|
92
|
-
TASK:
|
|
93
|
-
Generate a 'State of the Union' report for the Founder (The User).
|
|
94
|
-
|
|
95
|
-
CONTEXT:
|
|
96
|
-
1. VISION (Where we are going):
|
|
97
|
-
{context.get('vision', 'No vision file found.')}
|
|
98
|
-
|
|
99
|
-
2. TASKS (What is pending):
|
|
100
|
-
{context.get('tasks', 'No task file found.')}
|
|
101
|
-
|
|
102
|
-
3. INVISIBLE MACHINERY (What is running in background):
|
|
103
|
-
CRON: {context.get('cron')}
|
|
104
|
-
LOGS: {context.get('logs')}
|
|
105
|
-
|
|
106
|
-
INSTRUCTIONS:
|
|
107
|
-
- Write a clear, high-level Roadmap Report.
|
|
108
|
-
- Group status into:
|
|
109
|
-
1. ✅ Completed Capabilities (The Iron Man Suit)
|
|
110
|
-
2. ⚙️ Running Machinery (Invisible Work - cite logs)
|
|
111
|
-
3. 🚧 Active Construction (Next Phase)
|
|
112
|
-
- Clarify the "Mac vs Cloud" status explicitly.
|
|
113
|
-
- End with a concrete recommendation for the NEXT move.
|
|
114
|
-
|
|
115
|
-
FORMAT:
|
|
116
|
-
Markdown. Use Emoji. Be concise.
|
|
117
|
-
"""
|
|
118
|
-
|
|
119
|
-
try:
|
|
120
|
-
client = genai.Client(api_key=api_key)
|
|
121
|
-
response = client.models.generate_content(
|
|
122
|
-
model="gemini-2.0-flash-exp",
|
|
123
|
-
contents=prompt
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
report = response.text
|
|
127
|
-
return {
|
|
128
|
-
"status": "success",
|
|
129
|
-
"report": report
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
except Exception as e:
|
|
133
|
-
logger.error(f"Status Generation failed: {e}")
|
|
134
|
-
return {"status": "error", "message": str(e)}
|