nc1709 1.15.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nc1709/__init__.py +13 -0
- nc1709/agent/__init__.py +36 -0
- nc1709/agent/core.py +505 -0
- nc1709/agent/mcp_bridge.py +245 -0
- nc1709/agent/permissions.py +298 -0
- nc1709/agent/tools/__init__.py +21 -0
- nc1709/agent/tools/base.py +440 -0
- nc1709/agent/tools/bash_tool.py +367 -0
- nc1709/agent/tools/file_tools.py +454 -0
- nc1709/agent/tools/notebook_tools.py +516 -0
- nc1709/agent/tools/search_tools.py +322 -0
- nc1709/agent/tools/task_tool.py +284 -0
- nc1709/agent/tools/web_tools.py +555 -0
- nc1709/agents/__init__.py +17 -0
- nc1709/agents/auto_fix.py +506 -0
- nc1709/agents/test_generator.py +507 -0
- nc1709/checkpoints.py +372 -0
- nc1709/cli.py +3380 -0
- nc1709/cli_ui.py +1080 -0
- nc1709/cognitive/__init__.py +149 -0
- nc1709/cognitive/anticipation.py +594 -0
- nc1709/cognitive/context_engine.py +1046 -0
- nc1709/cognitive/council.py +824 -0
- nc1709/cognitive/learning.py +761 -0
- nc1709/cognitive/router.py +583 -0
- nc1709/cognitive/system.py +519 -0
- nc1709/config.py +155 -0
- nc1709/custom_commands.py +300 -0
- nc1709/executor.py +333 -0
- nc1709/file_controller.py +354 -0
- nc1709/git_integration.py +308 -0
- nc1709/github_integration.py +477 -0
- nc1709/image_input.py +446 -0
- nc1709/linting.py +519 -0
- nc1709/llm_adapter.py +667 -0
- nc1709/logger.py +192 -0
- nc1709/mcp/__init__.py +18 -0
- nc1709/mcp/client.py +370 -0
- nc1709/mcp/manager.py +407 -0
- nc1709/mcp/protocol.py +210 -0
- nc1709/mcp/server.py +473 -0
- nc1709/memory/__init__.py +20 -0
- nc1709/memory/embeddings.py +325 -0
- nc1709/memory/indexer.py +474 -0
- nc1709/memory/sessions.py +432 -0
- nc1709/memory/vector_store.py +451 -0
- nc1709/models/__init__.py +86 -0
- nc1709/models/detector.py +377 -0
- nc1709/models/formats.py +315 -0
- nc1709/models/manager.py +438 -0
- nc1709/models/registry.py +497 -0
- nc1709/performance/__init__.py +343 -0
- nc1709/performance/cache.py +705 -0
- nc1709/performance/pipeline.py +611 -0
- nc1709/performance/tiering.py +543 -0
- nc1709/plan_mode.py +362 -0
- nc1709/plugins/__init__.py +17 -0
- nc1709/plugins/agents/__init__.py +18 -0
- nc1709/plugins/agents/django_agent.py +912 -0
- nc1709/plugins/agents/docker_agent.py +623 -0
- nc1709/plugins/agents/fastapi_agent.py +887 -0
- nc1709/plugins/agents/git_agent.py +731 -0
- nc1709/plugins/agents/nextjs_agent.py +867 -0
- nc1709/plugins/base.py +359 -0
- nc1709/plugins/manager.py +411 -0
- nc1709/plugins/registry.py +337 -0
- nc1709/progress.py +443 -0
- nc1709/prompts/__init__.py +22 -0
- nc1709/prompts/agent_system.py +180 -0
- nc1709/prompts/task_prompts.py +340 -0
- nc1709/prompts/unified_prompt.py +133 -0
- nc1709/reasoning_engine.py +541 -0
- nc1709/remote_client.py +266 -0
- nc1709/shell_completions.py +349 -0
- nc1709/slash_commands.py +649 -0
- nc1709/task_classifier.py +408 -0
- nc1709/version_check.py +177 -0
- nc1709/web/__init__.py +8 -0
- nc1709/web/server.py +950 -0
- nc1709/web/templates/index.html +1127 -0
- nc1709-1.15.4.dist-info/METADATA +858 -0
- nc1709-1.15.4.dist-info/RECORD +86 -0
- nc1709-1.15.4.dist-info/WHEEL +5 -0
- nc1709-1.15.4.dist-info/entry_points.txt +2 -0
- nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
- nc1709-1.15.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin Manager for NC1709
|
|
3
|
+
Manages plugin lifecycle and execution
|
|
4
|
+
"""
|
|
5
|
+
from typing import Dict, List, Optional, Any, Tuple
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .base import Plugin, PluginStatus, PluginCapability, ActionResult
|
|
9
|
+
from .registry import PluginRegistry, PluginInfo
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PluginManager:
|
|
13
|
+
"""
|
|
14
|
+
Manages plugin lifecycle, configuration, and execution.
|
|
15
|
+
|
|
16
|
+
Handles:
|
|
17
|
+
- Plugin loading/unloading
|
|
18
|
+
- Configuration management
|
|
19
|
+
- Request routing to appropriate plugins
|
|
20
|
+
- Action execution with confirmation
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
24
|
+
"""Initialize the plugin manager
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
config: Global plugin configuration
|
|
28
|
+
"""
|
|
29
|
+
self._config = config or {}
|
|
30
|
+
self._registry = PluginRegistry()
|
|
31
|
+
self._instances: Dict[str, Plugin] = {}
|
|
32
|
+
self._load_order: List[str] = []
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def registry(self) -> PluginRegistry:
|
|
36
|
+
"""Get the plugin registry"""
|
|
37
|
+
return self._registry
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def loaded_plugins(self) -> Dict[str, Plugin]:
|
|
41
|
+
"""Get all loaded plugin instances"""
|
|
42
|
+
return self._instances.copy()
|
|
43
|
+
|
|
44
|
+
def discover_plugins(self) -> int:
|
|
45
|
+
"""Discover available plugins
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Number of plugins discovered
|
|
49
|
+
"""
|
|
50
|
+
return self._registry.discover()
|
|
51
|
+
|
|
52
|
+
def load_plugin(self, plugin_name: str, config: Optional[Dict] = None) -> bool:
|
|
53
|
+
"""Load a plugin by name
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
plugin_name: Name of plugin to load
|
|
57
|
+
config: Plugin-specific configuration
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if loaded successfully
|
|
61
|
+
"""
|
|
62
|
+
# Check if already loaded
|
|
63
|
+
if plugin_name in self._instances:
|
|
64
|
+
instance = self._instances[plugin_name]
|
|
65
|
+
if instance.status == PluginStatus.ACTIVE:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# Get plugin info from registry
|
|
69
|
+
info = self._registry.get(plugin_name)
|
|
70
|
+
if info is None:
|
|
71
|
+
print(f"Plugin '{plugin_name}' not found in registry")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
# Resolve and load dependencies first
|
|
75
|
+
dependencies = self._registry.resolve_dependencies(plugin_name)
|
|
76
|
+
for dep_name in dependencies[:-1]: # Exclude the plugin itself
|
|
77
|
+
if dep_name not in self._instances:
|
|
78
|
+
if not self.load_plugin(dep_name):
|
|
79
|
+
print(f"Failed to load dependency: {dep_name}")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# Merge configuration
|
|
83
|
+
plugin_config = {}
|
|
84
|
+
if plugin_name in self._config:
|
|
85
|
+
plugin_config.update(self._config[plugin_name])
|
|
86
|
+
if config:
|
|
87
|
+
plugin_config.update(config)
|
|
88
|
+
|
|
89
|
+
# Create and load instance
|
|
90
|
+
try:
|
|
91
|
+
instance = info.plugin_class(plugin_config)
|
|
92
|
+
if instance.load():
|
|
93
|
+
self._instances[plugin_name] = instance
|
|
94
|
+
self._load_order.append(plugin_name)
|
|
95
|
+
return True
|
|
96
|
+
else:
|
|
97
|
+
print(f"Plugin '{plugin_name}' failed to load: {instance.error}")
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"Error creating plugin '{plugin_name}': {e}")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
def unload_plugin(self, plugin_name: str) -> bool:
|
|
105
|
+
"""Unload a plugin
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
plugin_name: Name of plugin to unload
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
True if unloaded
|
|
112
|
+
"""
|
|
113
|
+
if plugin_name not in self._instances:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
# Check if other plugins depend on this one
|
|
117
|
+
dependents = self._find_dependents(plugin_name)
|
|
118
|
+
if dependents:
|
|
119
|
+
# Unload dependents first
|
|
120
|
+
for dep in dependents:
|
|
121
|
+
self.unload_plugin(dep)
|
|
122
|
+
|
|
123
|
+
instance = self._instances[plugin_name]
|
|
124
|
+
if instance.unload():
|
|
125
|
+
del self._instances[plugin_name]
|
|
126
|
+
if plugin_name in self._load_order:
|
|
127
|
+
self._load_order.remove(plugin_name)
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def reload_plugin(self, plugin_name: str) -> bool:
|
|
133
|
+
"""Reload a plugin
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
plugin_name: Plugin to reload
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if reloaded
|
|
140
|
+
"""
|
|
141
|
+
config = None
|
|
142
|
+
if plugin_name in self._instances:
|
|
143
|
+
config = self._instances[plugin_name].config.copy()
|
|
144
|
+
self.unload_plugin(plugin_name)
|
|
145
|
+
|
|
146
|
+
return self.load_plugin(plugin_name, config)
|
|
147
|
+
|
|
148
|
+
def load_all(self, enabled_only: bool = True) -> int:
|
|
149
|
+
"""Load all registered plugins
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
enabled_only: Only load plugins enabled by default
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Number of plugins loaded
|
|
156
|
+
"""
|
|
157
|
+
count = 0
|
|
158
|
+
|
|
159
|
+
for name, info in self._registry.plugins.items():
|
|
160
|
+
if enabled_only and not info.metadata.enabled_by_default:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
if self.load_plugin(name):
|
|
164
|
+
count += 1
|
|
165
|
+
|
|
166
|
+
return count
|
|
167
|
+
|
|
168
|
+
def unload_all(self) -> None:
|
|
169
|
+
"""Unload all plugins"""
|
|
170
|
+
# Unload in reverse order
|
|
171
|
+
for name in reversed(self._load_order.copy()):
|
|
172
|
+
self.unload_plugin(name)
|
|
173
|
+
|
|
174
|
+
def get_plugin(self, plugin_name: str) -> Optional[Plugin]:
|
|
175
|
+
"""Get a loaded plugin instance
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
plugin_name: Plugin name
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Plugin instance or None
|
|
182
|
+
"""
|
|
183
|
+
return self._instances.get(plugin_name)
|
|
184
|
+
|
|
185
|
+
def execute_action(
|
|
186
|
+
self,
|
|
187
|
+
plugin_name: str,
|
|
188
|
+
action_name: str,
|
|
189
|
+
confirm_dangerous: bool = True,
|
|
190
|
+
**kwargs
|
|
191
|
+
) -> ActionResult:
|
|
192
|
+
"""Execute a plugin action
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
plugin_name: Plugin name
|
|
196
|
+
action_name: Action to execute
|
|
197
|
+
confirm_dangerous: Ask for confirmation on dangerous actions
|
|
198
|
+
**kwargs: Action parameters
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
ActionResult
|
|
202
|
+
"""
|
|
203
|
+
plugin = self._instances.get(plugin_name)
|
|
204
|
+
if plugin is None:
|
|
205
|
+
return ActionResult.fail(f"Plugin '{plugin_name}' not loaded")
|
|
206
|
+
|
|
207
|
+
if plugin.status != PluginStatus.ACTIVE:
|
|
208
|
+
return ActionResult.fail(
|
|
209
|
+
f"Plugin '{plugin_name}' not active (status: {plugin.status.value})"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
action = plugin.actions.get(action_name)
|
|
213
|
+
if action is None:
|
|
214
|
+
return ActionResult.fail(
|
|
215
|
+
f"Action '{action_name}' not found in plugin '{plugin_name}'"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Check for dangerous action
|
|
219
|
+
if confirm_dangerous and action.dangerous:
|
|
220
|
+
# In a real implementation, this would prompt the user
|
|
221
|
+
# For now, we'll just note it in the result
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
return plugin.execute(action_name, **kwargs)
|
|
225
|
+
|
|
226
|
+
def find_handler(self, request: str) -> List[Tuple[str, float]]:
|
|
227
|
+
"""Find plugins that can handle a request
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
request: User's natural language request
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List of (plugin_name, confidence) tuples, sorted by confidence
|
|
234
|
+
"""
|
|
235
|
+
candidates = []
|
|
236
|
+
|
|
237
|
+
for name, plugin in self._instances.items():
|
|
238
|
+
if plugin.status != PluginStatus.ACTIVE:
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
confidence = plugin.can_handle(request)
|
|
242
|
+
if confidence > 0:
|
|
243
|
+
candidates.append((name, confidence))
|
|
244
|
+
|
|
245
|
+
# Sort by confidence (highest first)
|
|
246
|
+
candidates.sort(key=lambda x: x[1], reverse=True)
|
|
247
|
+
return candidates
|
|
248
|
+
|
|
249
|
+
def route_request(self, request: str, **kwargs) -> Optional[ActionResult]:
|
|
250
|
+
"""Route a request to the most suitable plugin
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
request: User's natural language request
|
|
254
|
+
**kwargs: Additional parameters
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
ActionResult or None if no handler found
|
|
258
|
+
"""
|
|
259
|
+
handlers = self.find_handler(request)
|
|
260
|
+
|
|
261
|
+
if not handlers:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
# Try handlers in order of confidence
|
|
265
|
+
for plugin_name, confidence in handlers:
|
|
266
|
+
plugin = self._instances[plugin_name]
|
|
267
|
+
|
|
268
|
+
# Let plugin decide which action to use
|
|
269
|
+
if hasattr(plugin, 'handle_request'):
|
|
270
|
+
result = plugin.handle_request(request, **kwargs)
|
|
271
|
+
if result and result.success:
|
|
272
|
+
return result
|
|
273
|
+
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
def get_all_actions(self) -> Dict[str, List[Dict]]:
|
|
277
|
+
"""Get all available actions from loaded plugins
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Dict mapping plugin names to their actions
|
|
281
|
+
"""
|
|
282
|
+
actions = {}
|
|
283
|
+
|
|
284
|
+
for name, plugin in self._instances.items():
|
|
285
|
+
if plugin.status != PluginStatus.ACTIVE:
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
plugin_actions = []
|
|
289
|
+
for action_name, action in plugin.actions.items():
|
|
290
|
+
plugin_actions.append({
|
|
291
|
+
"name": action_name,
|
|
292
|
+
"description": action.description,
|
|
293
|
+
"parameters": action.parameters,
|
|
294
|
+
"dangerous": action.dangerous,
|
|
295
|
+
"requires_confirmation": action.requires_confirmation
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
actions[name] = plugin_actions
|
|
299
|
+
|
|
300
|
+
return actions
|
|
301
|
+
|
|
302
|
+
def find_by_capability(
|
|
303
|
+
self,
|
|
304
|
+
capability: PluginCapability,
|
|
305
|
+
loaded_only: bool = True
|
|
306
|
+
) -> List[str]:
|
|
307
|
+
"""Find plugins with a specific capability
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
capability: Capability to search for
|
|
311
|
+
loaded_only: Only return loaded plugins
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of plugin names
|
|
315
|
+
"""
|
|
316
|
+
infos = self._registry.find_by_capability(capability)
|
|
317
|
+
names = [info.metadata.name for info in infos]
|
|
318
|
+
|
|
319
|
+
if loaded_only:
|
|
320
|
+
names = [n for n in names if n in self._instances]
|
|
321
|
+
|
|
322
|
+
return names
|
|
323
|
+
|
|
324
|
+
def get_status(self) -> Dict[str, Dict]:
|
|
325
|
+
"""Get status of all plugins
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dict with plugin statuses
|
|
329
|
+
"""
|
|
330
|
+
status = {}
|
|
331
|
+
|
|
332
|
+
for name, info in self._registry.plugins.items():
|
|
333
|
+
plugin_status = {
|
|
334
|
+
"registered": True,
|
|
335
|
+
"loaded": name in self._instances,
|
|
336
|
+
"status": "unloaded",
|
|
337
|
+
"version": info.metadata.version,
|
|
338
|
+
"builtin": info.is_builtin
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if name in self._instances:
|
|
342
|
+
instance = self._instances[name]
|
|
343
|
+
plugin_status["status"] = instance.status.value
|
|
344
|
+
plugin_status["error"] = instance.error
|
|
345
|
+
|
|
346
|
+
status[name] = plugin_status
|
|
347
|
+
|
|
348
|
+
return status
|
|
349
|
+
|
|
350
|
+
def configure_plugin(self, plugin_name: str, config: Dict[str, Any]) -> bool:
|
|
351
|
+
"""Update plugin configuration
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
plugin_name: Plugin to configure
|
|
355
|
+
config: New configuration values
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
True if configured successfully
|
|
359
|
+
"""
|
|
360
|
+
if plugin_name in self._instances:
|
|
361
|
+
return self._instances[plugin_name].configure(config)
|
|
362
|
+
|
|
363
|
+
# Store for later loading
|
|
364
|
+
if plugin_name not in self._config:
|
|
365
|
+
self._config[plugin_name] = {}
|
|
366
|
+
self._config[plugin_name].update(config)
|
|
367
|
+
return True
|
|
368
|
+
|
|
369
|
+
def _find_dependents(self, plugin_name: str) -> List[str]:
|
|
370
|
+
"""Find plugins that depend on the given plugin
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
plugin_name: Plugin name
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
List of dependent plugin names
|
|
377
|
+
"""
|
|
378
|
+
dependents = []
|
|
379
|
+
|
|
380
|
+
for name in self._instances:
|
|
381
|
+
deps = self._registry.get_dependencies(name)
|
|
382
|
+
if plugin_name in deps:
|
|
383
|
+
dependents.append(name)
|
|
384
|
+
|
|
385
|
+
return dependents
|
|
386
|
+
|
|
387
|
+
def get_help(self, plugin_name: Optional[str] = None) -> str:
|
|
388
|
+
"""Get help text for plugins
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
plugin_name: Specific plugin (None for all)
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Help text
|
|
395
|
+
"""
|
|
396
|
+
if plugin_name:
|
|
397
|
+
plugin = self._instances.get(plugin_name)
|
|
398
|
+
if plugin:
|
|
399
|
+
return plugin.get_help()
|
|
400
|
+
return f"Plugin '{plugin_name}' not loaded"
|
|
401
|
+
|
|
402
|
+
# General help
|
|
403
|
+
lines = ["# NC1709 Plugins", ""]
|
|
404
|
+
|
|
405
|
+
for name, plugin in self._instances.items():
|
|
406
|
+
if plugin.status == PluginStatus.ACTIVE:
|
|
407
|
+
lines.append(f"## {plugin.metadata.name}")
|
|
408
|
+
lines.append(f"{plugin.metadata.description}")
|
|
409
|
+
lines.append("")
|
|
410
|
+
|
|
411
|
+
return "\n".join(lines)
|