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,99 +0,0 @@
|
|
|
1
|
-
# Auto-generated from monolith decomposition
|
|
2
|
-
# =============================================================================
|
|
3
|
-
# v0.6.0 PROTOCOL COUPLING FIX - Tiered Tool Registration
|
|
4
|
-
# =============================================================================
|
|
5
|
-
# This wrapper ensures only tier-appropriate tools are registered with FastMCP.
|
|
6
|
-
# Without this, ALL tools would be registered regardless of NUCLEUS_TOOL_TIER.
|
|
7
|
-
# See: TITAN_HANDOVER_PROTOCOL.md Section 0 (Registry Bloat Solution)
|
|
8
|
-
|
|
9
|
-
from ..tool_tiers import is_tool_allowed, tier_manager
|
|
10
|
-
import sys
|
|
11
|
-
|
|
12
|
-
# State flag to prevent recursion when FastMCP internally calls mcp.tool
|
|
13
|
-
_REGISTERING_TOOL = False
|
|
14
|
-
_original_mcp_tool = None
|
|
15
|
-
|
|
16
|
-
def _tiered_tool_wrapper(*args, **kwargs):
|
|
17
|
-
"""
|
|
18
|
-
Wrapper around mcp.tool() that checks tier before registration.
|
|
19
|
-
|
|
20
|
-
Handles both decorator styles:
|
|
21
|
-
- @mcp.tool (func passed directly)
|
|
22
|
-
- @mcp.tool() (func is None, returns decorator)
|
|
23
|
-
"""
|
|
24
|
-
global _REGISTERING_TOOL, _original_mcp_tool
|
|
25
|
-
|
|
26
|
-
if _original_mcp_tool is None:
|
|
27
|
-
raise RuntimeError("Tiered tool registration not configured. Call configure_tiered_tool_registration(mcp) first.")
|
|
28
|
-
|
|
29
|
-
# If we are already in the middle of a registration, use the original method
|
|
30
|
-
if _REGISTERING_TOOL:
|
|
31
|
-
return _original_mcp_tool(*args, **kwargs)
|
|
32
|
-
|
|
33
|
-
func = None
|
|
34
|
-
if len(args) == 1 and callable(args[0]):
|
|
35
|
-
func = args[0]
|
|
36
|
-
args = args[1:]
|
|
37
|
-
|
|
38
|
-
def decorator(fn):
|
|
39
|
-
global _REGISTERING_TOOL
|
|
40
|
-
tool_name = fn.__name__
|
|
41
|
-
allowed = is_tool_allowed(tool_name)
|
|
42
|
-
if allowed:
|
|
43
|
-
tier_manager.registered_tools.add(tool_name)
|
|
44
|
-
|
|
45
|
-
# Set flag and call original method
|
|
46
|
-
_REGISTERING_TOOL = True
|
|
47
|
-
try:
|
|
48
|
-
# Register and capture the FunctionTool return
|
|
49
|
-
tool = _original_mcp_tool(*args, **kwargs)(fn)
|
|
50
|
-
|
|
51
|
-
# Make the FunctionTool object callable by proxying to the original function
|
|
52
|
-
if not callable(tool):
|
|
53
|
-
class CallableTool:
|
|
54
|
-
def __init__(self, tool, original_fn):
|
|
55
|
-
self._tool = tool
|
|
56
|
-
self._fn = original_fn
|
|
57
|
-
# Copy metadata
|
|
58
|
-
self.__name__ = original_fn.__name__
|
|
59
|
-
self.__doc__ = original_fn.__doc__
|
|
60
|
-
self.__module__ = original_fn.__module__
|
|
61
|
-
|
|
62
|
-
def __call__(self, *args, **kwargs):
|
|
63
|
-
print(f"[NUCLEUS] Executing {self.__name__}...", file=sys.stderr)
|
|
64
|
-
return self._fn(*args, **kwargs)
|
|
65
|
-
|
|
66
|
-
def __getattr__(self, name):
|
|
67
|
-
return getattr(self._tool, name)
|
|
68
|
-
|
|
69
|
-
# Pydantic serialization helpers
|
|
70
|
-
def model_dump(self, *args, **kwargs):
|
|
71
|
-
return self._tool.model_dump(*args, **kwargs)
|
|
72
|
-
|
|
73
|
-
def model_dump_json(self, *args, **kwargs):
|
|
74
|
-
return self._tool.model_dump_json(*args, **kwargs)
|
|
75
|
-
|
|
76
|
-
return CallableTool(tool, fn)
|
|
77
|
-
|
|
78
|
-
return tool
|
|
79
|
-
except Exception as e:
|
|
80
|
-
print(f"[NUCLEUS] ERROR registering {tool_name}: {e}", file=sys.stderr)
|
|
81
|
-
raise e
|
|
82
|
-
finally:
|
|
83
|
-
_REGISTERING_TOOL = False
|
|
84
|
-
else:
|
|
85
|
-
tier_manager.filtered_tools.add(tool_name)
|
|
86
|
-
# Return plain function - NOT registered with MCP
|
|
87
|
-
return fn
|
|
88
|
-
|
|
89
|
-
if func is not None:
|
|
90
|
-
return decorator(func)
|
|
91
|
-
|
|
92
|
-
return decorator
|
|
93
|
-
|
|
94
|
-
def configure_tiered_tool_registration(mcp_instance):
|
|
95
|
-
"""Initializes the tiered tool registration system for the given MCP instance."""
|
|
96
|
-
global _original_mcp_tool
|
|
97
|
-
_original_mcp_tool = mcp_instance.tool
|
|
98
|
-
mcp_instance.tool = _tiered_tool_wrapper
|
|
99
|
-
return mcp_instance
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import fcntl
|
|
2
|
-
import os
|
|
3
|
-
import time
|
|
4
|
-
import errno
|
|
5
|
-
import logging
|
|
6
|
-
from contextlib import contextmanager
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
class BrainLock:
|
|
11
|
-
"""
|
|
12
|
-
The Atomic Guard for the Sovereign Brain.
|
|
13
|
-
|
|
14
|
-
Implements cross-process file locking using fcntl.
|
|
15
|
-
Prevents race conditions between the MCP Server (User Interface)
|
|
16
|
-
and the Daemon (Background Worker).
|
|
17
|
-
|
|
18
|
-
Principles:
|
|
19
|
-
1. Safety: Never corrupt the Graph.
|
|
20
|
-
2. Liveness: Auto-break stale locks (App crashes).
|
|
21
|
-
3. Atomicity: All or nothing.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self, lock_file: str, timeout: int = 5):
|
|
25
|
-
"""
|
|
26
|
-
Initialize the BrainLock.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
lock_file: Path to the lock file (e.g., .brain/lock/state.lock)
|
|
30
|
-
timeout: Seconds to wait for lock acquisition before giving up.
|
|
31
|
-
"""
|
|
32
|
-
self.lock_file = lock_file
|
|
33
|
-
self.timeout = timeout
|
|
34
|
-
self.fd = None
|
|
35
|
-
self._ensure_dir()
|
|
36
|
-
|
|
37
|
-
def _ensure_dir(self):
|
|
38
|
-
os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
|
|
39
|
-
|
|
40
|
-
def acquire(self) -> bool:
|
|
41
|
-
"""
|
|
42
|
-
Acquire egg-lock. Blocking with timeout.
|
|
43
|
-
"""
|
|
44
|
-
start_time = time.time()
|
|
45
|
-
while True:
|
|
46
|
-
try:
|
|
47
|
-
self.fd = open(self.lock_file, 'w')
|
|
48
|
-
# LOCK_EX: Exclusive Lock
|
|
49
|
-
# LOCK_NB: Non-blocking (fail immediately if locked)
|
|
50
|
-
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
51
|
-
|
|
52
|
-
# Write PID for debugging / stale detection
|
|
53
|
-
self.fd.write(str(os.getpid()))
|
|
54
|
-
self.fd.flush()
|
|
55
|
-
return True
|
|
56
|
-
|
|
57
|
-
except IOError as e:
|
|
58
|
-
if self.fd:
|
|
59
|
-
self.fd.close()
|
|
60
|
-
self.fd = None
|
|
61
|
-
|
|
62
|
-
if e.errno != errno.EAGAIN:
|
|
63
|
-
# Unexpected error
|
|
64
|
-
logger.error(f"BrainLock Error: {e}")
|
|
65
|
-
raise
|
|
66
|
-
|
|
67
|
-
# Locked by another process
|
|
68
|
-
if time.time() - start_time >= self.timeout:
|
|
69
|
-
# Timeout reached
|
|
70
|
-
logger.warning(f"BrainLock Acquisition Timeout: {self.lock_file}")
|
|
71
|
-
return False
|
|
72
|
-
|
|
73
|
-
# Wait and retry
|
|
74
|
-
time.sleep(0.1)
|
|
75
|
-
|
|
76
|
-
def release(self):
|
|
77
|
-
"""
|
|
78
|
-
Release the lock.
|
|
79
|
-
"""
|
|
80
|
-
if self.fd:
|
|
81
|
-
try:
|
|
82
|
-
# Remove the lock file content (optional, but clean)
|
|
83
|
-
self.fd.truncate(0)
|
|
84
|
-
fcntl.flock(self.fd, fcntl.LOCK_UN)
|
|
85
|
-
except Exception as e:
|
|
86
|
-
logger.error(f"BrainLock Release Error: {e}")
|
|
87
|
-
finally:
|
|
88
|
-
self.fd.close()
|
|
89
|
-
self.fd = None
|
|
90
|
-
|
|
91
|
-
@contextmanager
|
|
92
|
-
def guard(self):
|
|
93
|
-
"""
|
|
94
|
-
Context Manager for 'with BrainLock(...):' syntax.
|
|
95
|
-
"""
|
|
96
|
-
if not self.acquire():
|
|
97
|
-
raise TimeoutError(f"Could not acquire BrainLock on {self.lock_file}")
|
|
98
|
-
try:
|
|
99
|
-
yield
|
|
100
|
-
finally:
|
|
101
|
-
self.release()
|
|
102
|
-
|
|
103
|
-
@staticmethod
|
|
104
|
-
def is_locked(lock_file: str) -> bool:
|
|
105
|
-
"""
|
|
106
|
-
Check if a file is currently locked without waiting.
|
|
107
|
-
"""
|
|
108
|
-
if not os.path.exists(lock_file):
|
|
109
|
-
return False
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
fd = open(lock_file, 'r')
|
|
113
|
-
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
114
|
-
# If we got here, it wasn't locked. Unlock and close.
|
|
115
|
-
fcntl.flock(fd, fcntl.LOCK_UN)
|
|
116
|
-
fd.close()
|
|
117
|
-
return False
|
|
118
|
-
except IOError as e:
|
|
119
|
-
if e.errno == errno.EAGAIN:
|
|
120
|
-
return True
|
|
121
|
-
raise
|
|
File without changes
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import json
|
|
3
|
-
import logging
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Dict, Any
|
|
6
|
-
|
|
7
|
-
logger = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
|
-
class Injector:
|
|
10
|
-
"""
|
|
11
|
-
The Nucleus Hypervisor Injection Primitive (Layer 2).
|
|
12
|
-
Dynamically injects context and identity into running IDEs by manipulating
|
|
13
|
-
configuration files that support hot-reloading (e.g., .vscode/settings.json).
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
def __init__(self, workspace_root: str):
|
|
17
|
-
self.workspace_root = Path(workspace_root)
|
|
18
|
-
self.vscode_settings_path = self.workspace_root / ".vscode" / "settings.json"
|
|
19
|
-
|
|
20
|
-
def _ensure_vscode_dir(self):
|
|
21
|
-
if not self.vscode_settings_path.parent.exists():
|
|
22
|
-
self.vscode_settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
-
|
|
24
|
-
def _read_settings(self) -> Dict[str, Any]:
|
|
25
|
-
if not self.vscode_settings_path.exists():
|
|
26
|
-
return {}
|
|
27
|
-
try:
|
|
28
|
-
# Handle comments in JSON if necessary (standard json lib doesn't, but VSCode allows them)
|
|
29
|
-
# For now, assuming standard JSON or we overwrite.
|
|
30
|
-
# Ideally use a comment-preserving parser, but simple JSON is safer for machine-control.
|
|
31
|
-
return json.loads(self.vscode_settings_path.read_text())
|
|
32
|
-
except Exception as e:
|
|
33
|
-
logger.warning(f"Failed to read settings.json: {e}")
|
|
34
|
-
return {}
|
|
35
|
-
|
|
36
|
-
def _write_settings(self, settings: Dict[str, Any]) -> bool:
|
|
37
|
-
try:
|
|
38
|
-
self._ensure_vscode_dir()
|
|
39
|
-
self.vscode_settings_path.write_text(json.dumps(settings, indent=4))
|
|
40
|
-
return True
|
|
41
|
-
except Exception as e:
|
|
42
|
-
logger.error(f"Failed to write settings.json: {e}")
|
|
43
|
-
return False
|
|
44
|
-
|
|
45
|
-
def inject_identity(self, identity: str, color_hex: str) -> bool:
|
|
46
|
-
"""
|
|
47
|
-
Injects visual identity markers into VSCode settings.
|
|
48
|
-
"""
|
|
49
|
-
settings = self._read_settings()
|
|
50
|
-
|
|
51
|
-
# Inject standard visual cues
|
|
52
|
-
settings["workbench.colorCustomizations"] = settings.get("workbench.colorCustomizations", {})
|
|
53
|
-
settings["workbench.colorCustomizations"]["titleBar.activeBackground"] = color_hex
|
|
54
|
-
settings["workbench.colorCustomizations"]["titleBar.activeForeground"] = "#ffffff"
|
|
55
|
-
settings["workbench.colorCustomizations"]["activityBar.background"] = color_hex
|
|
56
|
-
|
|
57
|
-
# Inject status bar message (if possible via extension, or just window title)
|
|
58
|
-
settings["window.title"] = f"[{identity.upper()}] ${{rootName}}"
|
|
59
|
-
|
|
60
|
-
# Inject context-specific file exclusions if needed
|
|
61
|
-
# e.g., if Red Team, hide 'build' folder?
|
|
62
|
-
|
|
63
|
-
return self._write_settings(settings)
|
|
64
|
-
|
|
65
|
-
def reset_identity(self) -> bool:
|
|
66
|
-
"""
|
|
67
|
-
Removes injected identity markers.
|
|
68
|
-
"""
|
|
69
|
-
settings = self._read_settings()
|
|
70
|
-
|
|
71
|
-
if "workbench.colorCustomizations" in settings:
|
|
72
|
-
# Remove our specific keys
|
|
73
|
-
for key in ["titleBar.activeBackground", "titleBar.activeForeground", "activityBar.background"]:
|
|
74
|
-
settings["workbench.colorCustomizations"].pop(key, None)
|
|
75
|
-
|
|
76
|
-
# If empty, remove the whole object
|
|
77
|
-
if not settings["workbench.colorCustomizations"]:
|
|
78
|
-
del settings["workbench.colorCustomizations"]
|
|
79
|
-
|
|
80
|
-
if "window.title" in settings:
|
|
81
|
-
del settings["window.title"]
|
|
82
|
-
|
|
83
|
-
return self._write_settings(settings)
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import subprocess
|
|
3
|
-
import os
|
|
4
|
-
import logging
|
|
5
|
-
from typing import List
|
|
6
|
-
|
|
7
|
-
logger = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
|
-
class Locker:
|
|
10
|
-
"""
|
|
11
|
-
The Nucleus Hypervisor Locking Primitive (Layer 4).
|
|
12
|
-
Uses macOS 'chflags' (immutable flags) to enforce file-system level write protection.
|
|
13
|
-
This prevents even root/sudo users (and other agents) from modifying locked files
|
|
14
|
-
until the lock is explicitly released.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def __init__(self):
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
|
-
def _run_cmd(self, cmd: List[str]) -> bool:
|
|
21
|
-
try:
|
|
22
|
-
subprocess.run(
|
|
23
|
-
cmd,
|
|
24
|
-
capture_output=True,
|
|
25
|
-
text=True,
|
|
26
|
-
check=True
|
|
27
|
-
)
|
|
28
|
-
return True
|
|
29
|
-
except subprocess.CalledProcessError as e:
|
|
30
|
-
logger.error(f"Locker Command Failed: {e.stderr}")
|
|
31
|
-
return False
|
|
32
|
-
|
|
33
|
-
def lock(self, path: str, metadata: dict = None) -> bool:
|
|
34
|
-
"""
|
|
35
|
-
Locks a file or directory using 'chflags uchg'.
|
|
36
|
-
If path is a directory, it locks it recursively?
|
|
37
|
-
Current implementation: Single file/folder lock (non-recursive by default for safety).
|
|
38
|
-
To do recursive: chflags -R uchg
|
|
39
|
-
"""
|
|
40
|
-
if not os.path.exists(path):
|
|
41
|
-
logger.error(f"Cannot lock non-existent path: {path}")
|
|
42
|
-
return False
|
|
43
|
-
|
|
44
|
-
logger.info(f"🔒 Locking: {path}")
|
|
45
|
-
|
|
46
|
-
# Apply metadata BEFORE making immutable
|
|
47
|
-
if metadata:
|
|
48
|
-
import time
|
|
49
|
-
if "timestamp" not in metadata:
|
|
50
|
-
metadata["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
|
51
|
-
|
|
52
|
-
for k, v in metadata.items():
|
|
53
|
-
key = k if k.startswith("nucleus.lock.") else f"nucleus.lock.{k}"
|
|
54
|
-
# If directory, apply only to the directory itself for now (recursive xattr is tricky)
|
|
55
|
-
self._set_xattr(path, key, v)
|
|
56
|
-
|
|
57
|
-
# Using -R (recursive) to lock entire directories if needed
|
|
58
|
-
if os.path.isdir(path):
|
|
59
|
-
return self._run_cmd(["chflags", "-R", "uchg", path])
|
|
60
|
-
else:
|
|
61
|
-
return self._run_cmd(["chflags", "uchg", path])
|
|
62
|
-
|
|
63
|
-
def unlock(self, path: str) -> bool:
|
|
64
|
-
"""
|
|
65
|
-
Unlocks a file or directory using 'chflags nouchg'.
|
|
66
|
-
"""
|
|
67
|
-
if not os.path.exists(path):
|
|
68
|
-
logger.error(f"Cannot unlock non-existent path: {path}")
|
|
69
|
-
return False
|
|
70
|
-
|
|
71
|
-
logger.info(f"🔓 Unlocking: {path}")
|
|
72
|
-
if os.path.isdir(path):
|
|
73
|
-
return self._run_cmd(["chflags", "-R", "nouchg", path])
|
|
74
|
-
else:
|
|
75
|
-
return self._run_cmd(["chflags", "nouchg", path])
|
|
76
|
-
|
|
77
|
-
def is_locked(self, path: str) -> bool:
|
|
78
|
-
"""
|
|
79
|
-
Checks if the file has the 'uchg' flag set.
|
|
80
|
-
"""
|
|
81
|
-
if not os.path.exists(path):
|
|
82
|
-
return False
|
|
83
|
-
|
|
84
|
-
# 'ls -lz' shows flags on macOS, or 'ls -MdO'
|
|
85
|
-
# simpler: stat
|
|
86
|
-
try:
|
|
87
|
-
st = os.stat(path)
|
|
88
|
-
# 0x2 is UF_IMMUTABLE (uchg)
|
|
89
|
-
return (st.st_flags & 0x2) != 0
|
|
90
|
-
except Exception:
|
|
91
|
-
return False
|
|
92
|
-
|
|
93
|
-
# --- METADATA (Phase 28) ---
|
|
94
|
-
def _set_xattr(self, path: str, key: str, value: str):
|
|
95
|
-
try:
|
|
96
|
-
subprocess.run(
|
|
97
|
-
["xattr", "-w", key, str(value), path],
|
|
98
|
-
check=True,
|
|
99
|
-
capture_output=True
|
|
100
|
-
)
|
|
101
|
-
except subprocess.CalledProcessError as e:
|
|
102
|
-
logger.warning(f"Failed to set xattr {key}: {e}")
|
|
103
|
-
|
|
104
|
-
def get_metadata(self, path: str) -> dict:
|
|
105
|
-
"""Retrieves lock metadata from xattrs."""
|
|
106
|
-
if not os.path.exists(path):
|
|
107
|
-
return {}
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
result = subprocess.run(
|
|
111
|
-
["xattr", path],
|
|
112
|
-
check=True,
|
|
113
|
-
capture_output=True,
|
|
114
|
-
text=True
|
|
115
|
-
)
|
|
116
|
-
keys = result.stdout.strip().split("\n")
|
|
117
|
-
data = {}
|
|
118
|
-
for key in keys:
|
|
119
|
-
if key.startswith("nucleus.lock."):
|
|
120
|
-
val = subprocess.run(
|
|
121
|
-
["xattr", "-p", key, path],
|
|
122
|
-
check=True,
|
|
123
|
-
capture_output=True,
|
|
124
|
-
text=True
|
|
125
|
-
).stdout.strip()
|
|
126
|
-
clean_key = key.replace("nucleus.lock.", "")
|
|
127
|
-
data[clean_key] = val
|
|
128
|
-
return data
|
|
129
|
-
except Exception:
|
|
130
|
-
return {}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import logging
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from watchdog.observers import Observer
|
|
5
|
-
from watchdog.events import FileSystemEventHandler
|
|
6
|
-
|
|
7
|
-
from .locker import Locker
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
class SecurityEventHandler(FileSystemEventHandler):
|
|
12
|
-
"""
|
|
13
|
-
Handles file events for the Hypervisor Watchdog.
|
|
14
|
-
Detects attempts to modify locked files.
|
|
15
|
-
"""
|
|
16
|
-
def __init__(self, watchdog_instance, locker: Locker):
|
|
17
|
-
self.watchdog = watchdog_instance
|
|
18
|
-
self.locker = locker
|
|
19
|
-
|
|
20
|
-
def on_modified(self, event):
|
|
21
|
-
if event.is_directory:
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
path = str(Path(event.src_path).resolve())
|
|
25
|
-
is_protected = False
|
|
26
|
-
|
|
27
|
-
# Access live list from watchdog instance
|
|
28
|
-
for p_path in self.watchdog.protected_paths:
|
|
29
|
-
if path == p_path or path.startswith(p_path + "/"):
|
|
30
|
-
is_protected = True
|
|
31
|
-
break
|
|
32
|
-
|
|
33
|
-
if is_protected:
|
|
34
|
-
# Breach Detected!
|
|
35
|
-
logger.warning(f"🚨 SECURITY BREACH: Locked file modified: {path}")
|
|
36
|
-
|
|
37
|
-
# 1. Immediate Re-Lock (in case it was unlocked)
|
|
38
|
-
self.locker.lock(path)
|
|
39
|
-
|
|
40
|
-
# 2. (Future) Revert from Shadow Copy
|
|
41
|
-
# self.revert(path)
|
|
42
|
-
|
|
43
|
-
class Watchdog:
|
|
44
|
-
"""
|
|
45
|
-
The Nucleus Hypervisor Sentinel (Layer 1).
|
|
46
|
-
Monitors critical files and reinforces locks.
|
|
47
|
-
"""
|
|
48
|
-
def __init__(self, workspace_root: str):
|
|
49
|
-
self.workspace_root = Path(workspace_root)
|
|
50
|
-
self.locker = Locker()
|
|
51
|
-
self.observer = Observer()
|
|
52
|
-
self.protected_paths = []
|
|
53
|
-
|
|
54
|
-
def protect(self, path: str):
|
|
55
|
-
"""Adds a path to the protected list and starts watching it."""
|
|
56
|
-
abs_path = str((self.workspace_root / path).resolve())
|
|
57
|
-
if abs_path not in self.protected_paths:
|
|
58
|
-
self.protected_paths.append(abs_path)
|
|
59
|
-
# Ensure it is physically locked
|
|
60
|
-
self.locker.lock(abs_path)
|
|
61
|
-
|
|
62
|
-
def start(self):
|
|
63
|
-
"""Starts the background monitoring thread."""
|
|
64
|
-
handler = SecurityEventHandler(self, self.locker)
|
|
65
|
-
self.observer.schedule(handler, str(self.workspace_root), recursive=True)
|
|
66
|
-
self.observer.start()
|
|
67
|
-
logger.info(f"👁️ Hypervisor Watchdog started on {self.workspace_root}")
|
|
68
|
-
|
|
69
|
-
def stop(self):
|
|
70
|
-
self.observer.stop()
|
|
71
|
-
self.observer.join()
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Agent Runtime - Runtime Package
|
|
3
|
-
|
|
4
|
-
v0.6.0 DSoR: Added context_manager and ipc_auth for decision provenance.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from .factory import ContextFactory
|
|
8
|
-
from .agent import EphemeralAgent, DecisionMade, ActionRequested
|
|
9
|
-
from .event_stream import EventSeverity, EventTypes, emit_event, read_events
|
|
10
|
-
from .triggers import match_triggers, get_agents_for_event
|
|
11
|
-
from .profiling import (
|
|
12
|
-
timed, timed_io, timed_compute, timed_llm,
|
|
13
|
-
get_metrics, get_metrics_summary, reset_metrics, export_metrics_to_file
|
|
14
|
-
)
|
|
15
|
-
# v0.6.0 DSoR Components
|
|
16
|
-
from .context_manager import (
|
|
17
|
-
ContextManager, ContextSnapshot, StateVerificationResult,
|
|
18
|
-
get_context_manager, compute_context_hash, verify_turn_integrity
|
|
19
|
-
)
|
|
20
|
-
from .ipc_auth import (
|
|
21
|
-
IPCAuthManager, IPCToken, TokenMeterEntry,
|
|
22
|
-
get_ipc_auth_manager, require_ipc_token
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
__all__ = [
|
|
26
|
-
"ContextFactory",
|
|
27
|
-
"EphemeralAgent",
|
|
28
|
-
"DecisionMade",
|
|
29
|
-
"ActionRequested",
|
|
30
|
-
"EventSeverity",
|
|
31
|
-
"EventTypes",
|
|
32
|
-
"emit_event",
|
|
33
|
-
"read_events",
|
|
34
|
-
"match_triggers",
|
|
35
|
-
"get_agents_for_event",
|
|
36
|
-
# Profiling (AG-014)
|
|
37
|
-
"timed",
|
|
38
|
-
"timed_io",
|
|
39
|
-
"timed_compute",
|
|
40
|
-
"timed_llm",
|
|
41
|
-
"get_metrics",
|
|
42
|
-
"get_metrics_summary",
|
|
43
|
-
"reset_metrics",
|
|
44
|
-
"export_metrics_to_file",
|
|
45
|
-
# v0.6.0 DSoR - Context Manager
|
|
46
|
-
"ContextManager",
|
|
47
|
-
"ContextSnapshot",
|
|
48
|
-
"StateVerificationResult",
|
|
49
|
-
"get_context_manager",
|
|
50
|
-
"compute_context_hash",
|
|
51
|
-
"verify_turn_integrity",
|
|
52
|
-
# v0.6.0 DSoR - IPC Auth
|
|
53
|
-
"IPCAuthManager",
|
|
54
|
-
"IPCToken",
|
|
55
|
-
"TokenMeterEntry",
|
|
56
|
-
"get_ipc_auth_manager",
|
|
57
|
-
"require_ipc_token"
|
|
58
|
-
]
|