-
Notifications
You must be signed in to change notification settings - Fork 0
Add NeuroRift package scaffold, runtime & model checks, secure command runner, CLI and skill system #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add NeuroRift package scaffold, runtime & model checks, secure command runner, CLI and skill system #38
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,99 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Ollama model capability verification for autonomous NeuroRift agent mode.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CAPABILITY_PROMPT = """Evaluate whether you can reliably perform the following tasks: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * structured tool invocation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Linux command generation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * file creation and modification | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * interpreting tool outputs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * multi-step reasoning for autonomous agents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Return a JSON response: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "tool_usage": true/false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "command_generation": true/false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "filesystem_operations": true/false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "multi_step_reasoning": true/false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent_ready": true/false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _extract_json(raw_text: str) -> dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raw_text = (raw_text or "").strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start = raw_text.find("{") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end = raw_text.rfind("}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if start == -1 or end == -1 or end <= start: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError("No JSON object found in model response") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return json.loads(raw_text[start : end + 1]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def verify_model_capabilities(model_name: str) -> dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| proc = subprocess.run( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ["ollama", "run", model_name, CAPABILITY_PROMPT], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| capture_output=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout=120, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except FileNotFoundError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {"ok": False, "error": "ollama_missing", "agent_ready": False} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as exc: # defensive for unstable environments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ok": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "error": f"ollama_exec_error:{type(exc).__name__}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent_ready": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if proc.returncode != 0: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ok": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "error": f"ollama_returned_{proc.returncode}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "stderr": proc.stderr, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent_ready": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsed = _extract_json(proc.stdout) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as exc: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ok": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "error": f"invalid_capability_json:{type(exc).__name__}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "raw": proc.stdout[:1000], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent_ready": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| required = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "tool_usage", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "command_generation", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "filesystem_operations", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "multi_step_reasoning", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent_ready", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not required.issubset(parsed): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ok": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "error": "capability_fields_missing", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "parsed": parsed, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "agent_ready": False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parsed["ok"] = bool(parsed.get("agent_ready")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return parsed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Capability fields are only checked for presence, not type, so string values like Severity Level: Critical 🚨- ❌ Non-ready models can pass autonomous readiness gate.
- ⚠️ Autonomous execution reliability drops with weak model capability.
Suggested change
Steps of Reproduction ✅1. Trigger autonomous startup via `neurorift_cli.py:51-52` into `run-agent` path in
`neurorift_main.py:1007`.
2. Execute with a model output where capability fields are strings (e.g., `"agent_ready":
"false"`), which is realistic for LLM formatting drift; `verify_model_capabilities` parses
it at `model_capability_check.py:57-58`.
3. Current code only checks key presence (`model_capability_check.py:74`) and then
computes `ok` using `bool(parsed.get("agent_ready"))` (`model_capability_check.py:82`), so
`"false"` is treated truthy.
4. Caller checks raw `capability.get("agent_ready")` at `neurorift_main.py:1019`;
non-empty string bypasses rejection, allowing autonomous run with a non-ready model.Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** model_capability_check.py
**Line:** 74:83
**Comment:**
*Type Error: Capability fields are only checked for presence, not type, so string values like `"false"` are treated as truthy by callers and can incorrectly allow non-ready models to pass. Enforce boolean types for all required fields and compute readiness from a real boolean.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser = argparse.ArgumentParser( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description="Check Ollama model capability for NeuroRift agent mode" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parser.add_argument("--model", required=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args = parser.parse_args() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(json.dumps(verify_model_capabilities(args.model), indent=2)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class ChannelRouter: | ||
| def route(self, msg: dict): | ||
| return msg |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| class Channel: | ||
| def connect(self): | ||
| return True | ||
|
|
||
| def receive_message(self, payload=None): | ||
| return payload or {} | ||
|
|
||
| def send_message(self, message): | ||
| return message | ||
|
|
||
| def close(self): | ||
| return True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| class Channel: | ||
| def connect(self): | ||
| return True | ||
|
|
||
| def receive_message(self, payload=None): | ||
| return payload or {} | ||
|
|
||
| def send_message(self, message): | ||
| return message | ||
|
|
||
| def close(self): | ||
| return True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| class Channel: | ||
| def connect(self): | ||
| return True | ||
|
|
||
| def receive_message(self, payload=None): | ||
| return payload or {} | ||
|
|
||
| def send_message(self, message): | ||
| return message | ||
|
|
||
| def close(self): | ||
| return True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| class Channel: | ||
| def connect(self): | ||
| return True | ||
|
|
||
| def receive_message(self, payload=None): | ||
| return payload or {} | ||
|
|
||
| def send_message(self, message): | ||
| return message | ||
|
|
||
| def close(self): | ||
| return True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| class ClawHubAPI: | ||
| base_url = "https://clawhub.example/api" |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,18 @@ | ||||||||||
| from pathlib import Path | ||||||||||
| import shutil | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class ClawHubClient: | ||||||||||
| def __init__(self, cache_dir: Path): | ||||||||||
| self.cache_dir = cache_dir | ||||||||||
| cache_dir.mkdir(parents=True, exist_ok=True) | ||||||||||
|
|
||||||||||
| def fetch_skill(self, skill_name: str, source_dir: Path) -> Path: | ||||||||||
| src = source_dir / skill_name | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Severity Level: Critical 🚨- ⚠️ ClawHub install reads directories outside skill_store.
- ❌ Unauthorized local folder contents copied into cache.
Suggested change
Steps of Reproduction ✅1. Run CLI install flow with user-controlled argument: `python3 neurorift_main.py
--clawhub ../../..`; `--clawhub` is accepted at `neurorift_main.py:914` and executed at
`neurorift_main.py:983`.
2. Call chain is `main()` (`neurorift_main.py:1472`) → `_async_main()`
(`neurorift_main.py:973`) → `SkillManager.install_clawhub()`
(`neurorift/skills/skill_manager.py:13-14`) → `ClawHubClient.fetch_skill()`
(`neurorift/clawhub/clawhub_client.py:5-6`).
3. At `clawhub_client.py:6`, `src = source_dir / skill_name` allows `..` traversal, so
`src` can resolve outside `self.examples` (`skill_manager.py:12`) without any containment
check.
4. `shutil.copytree(src, dst)` at `clawhub_client.py:10` then copies an out-of-scope local
directory into cache before validation, proving source-directory escape.Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** neurorift/clawhub/clawhub_client.py
**Line:** 6:6
**Comment:**
*Security: `skill_name` is directly joined into the source path without containment checks, so values like `../...` can escape the intended skill directory and copy arbitrary local folders. Resolve the path and verify it still stays under `source_dir` before using it.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. |
||||||||||
| if not src.exists(): | ||||||||||
| raise FileNotFoundError(skill_name) | ||||||||||
| dst = self.cache_dir / skill_name | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: The destination path is also built from untrusted Severity Level: Critical 🚨- ❌ Recursive delete may target non-cache directories.
- ❌ Skill copy may overwrite paths outside cache.
Suggested change
Steps of Reproduction ✅1. Trigger ClawHub install path with traversal input, e.g. `python3 neurorift_main.py
--clawhub ../../..`; this argument is passed unchanged except `.strip()` at
`neurorift_main.py:983`.
2. The same call chain reaches `ClawHubClient.fetch_skill()`
(`neurorift/skills/skill_manager.py:14` → `neurorift/clawhub/clawhub_client.py:5`).
3. At `clawhub_client.py:8`, `dst` is built with untrusted `skill_name`; with `../../..`,
`dst` points outside `self.cache_dir` (created from `SkillInstaller.cache` at
`skill_manager.py:11`, `installer.py:8-9`).
4. If that external path exists, `shutil.rmtree(dst)` at `clawhub_client.py:9` attempts
recursive deletion outside cache, then `copytree` writes there at line 10.Prompt for AI Agent 🤖This is a comment left during a code review.
**Path:** neurorift/clawhub/clawhub_client.py
**Line:** 8:8
**Comment:**
*Security: The destination path is also built from untrusted `skill_name`, so traversal like `../../...` can make `rmtree` and `copytree` operate outside the cache directory. Resolve and enforce that the destination remains inside `cache_dir`.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise. |
||||||||||
| if dst.exists(): | ||||||||||
| shutil.rmtree(dst) | ||||||||||
| shutil.copytree(src, dst) | ||||||||||
| return dst | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class ClawHubResolver: | ||
| def resolve(self, skill_name: str) -> str: | ||
| return f"https://clawhub.example/skills/{skill_name}" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| """Package-level CLI helper that delegates to global entrypoint.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from neurorift_cli import build_parser, install_global_wrapper, main | ||
|
|
||
| __all__ = ["build_parser", "install_global_wrapper", "main"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| CHANNELS = ["cli", "websocket", "discord", "telegram", "api"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| MODEL_PROVIDERS = ["deepseek", "mistral", "llama", "openai"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| from dataclasses import dataclass | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| @dataclass | ||
| class Settings: | ||
| data_dir: Path = Path.home() / ".neurorift" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class AgentLoop: | ||
| async def handle_message(self, session, message: str) -> dict: | ||
| return {"success": True, "response": f"Session {session.session_id}: {message}"} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from dataclasses import dataclass | ||
| from neurorift.core.agent_loop import AgentLoop | ||
| from neurorift.sessions.session_manager import SessionManager | ||
|
|
||
|
|
||
| @dataclass | ||
| class AgentHandle: | ||
| session_id: str | ||
| user_id: str | ||
| channel: str | ||
|
|
||
|
|
||
| class AgentManager: | ||
| def __init__(self, session_manager: SessionManager, agent_loop: AgentLoop): | ||
| self.session_manager = session_manager | ||
| self.agent_loop = agent_loop | ||
| self.handles: dict[str, AgentHandle] = {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass | ||
| class PlannedStep: | ||
| order: int | ||
| action: str | ||
|
|
||
|
|
||
| class Planner: | ||
| def create_plan(self, objective: str) -> list[PlannedStep]: | ||
| return ( | ||
| [PlannedStep(order=1, action=f"Investigate: {objective}")] | ||
| if objective | ||
| else [] | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class TaskRouter: | ||
| def route(self, task: str) -> dict: | ||
| return {"task": task, "route": "agent_loop"} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,12 @@ | ||||||||||
| import subprocess | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class CommandRunner: | ||||||||||
| def run(self, cmd: list[str]): | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Calling Severity Level: Major
|
||||||||||
| def run(self, cmd: list[str]): | |
| def run(self, cmd: list[str]): | |
| if not cmd: | |
| return {"success": False, "stdout": "", "stderr": "empty command", "exit_code": -1} |
Steps of Reproduction ✅
1. Confirm actual execution surface: `CommandRunner` is defined at
`neurorift/execution/command_runner.py:2-5`, and `SandboxRunner` inherits it at
`neurorift/execution/sandbox_runner.py:1-3`.
2. Verify caller context: repo-wide search shows no current instantiation sites
(`CommandRunner(` has no matches), so direct module invocation is the only current
reproducible path.
3. Run `PYTHONPATH=/workspace/NeuroRift python3 -c "from
neurorift.execution.command_runner import CommandRunner; CommandRunner().run([])"`.
4. Observe crash at `neurorift/execution/command_runner.py:4` with `IndexError: list index
out of range` from `subprocess.run`, so method contract (dict result) is violated.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** neurorift/execution/command_runner.py
**Line:** 3:3
**Comment:**
*Logic Error: Calling `subprocess.run` with an empty command list raises `IndexError`, so this method can crash instead of returning the structured result object. Add an explicit empty-input check and return a failure payload when no executable is provided.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass | ||
| class ResourceLimits: | ||
| timeout_seconds: int = 30 | ||
| memory_limit_mb: int = 512 | ||
| cpu_seconds: int = 20 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import time | ||
|
|
||
|
|
||
| class RetryManager: | ||
| def __init__(self, retries=2): | ||
| self.retries = retries | ||
|
|
||
| def wait(self, attempt): | ||
| time.sleep(2**attempt) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| from neurorift.execution.command_runner import CommandRunner | ||
|
|
||
|
|
||
| class SandboxRunner(CommandRunner): | ||
| pass |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class APIServer: | ||
| def start(self): | ||
| return True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class AuthManager: | ||
| def validate(self, token: str): | ||
| return bool(token) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class WebSocketGateway: | ||
| def start(self): | ||
| return True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| class LongTermMemory: | ||
| def __init__(self): | ||
| self.data = {} | ||
|
|
||
| def store(self, user_id: str, key: str, value): | ||
| self.data.setdefault(user_id, {})[key] = value |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class MemoryCompaction: | ||
| def compact(self, messages: list[str]): | ||
| return {"summary": " ".join(messages[:10]), "kept": messages[-10:]} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| from collections import defaultdict, deque | ||
|
|
||
|
|
||
| class ShortTermMemory: | ||
| def __init__(self, limit: int = 30): | ||
| self.buffers = defaultdict(lambda: deque(maxlen=limit)) | ||
|
|
||
| def add(self, sid: str, msg: str): | ||
| self.buffers[sid].append(msg) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| class VectorMemory: | ||
| def semantic_search(self, user_id: str, query: str, top_k: int = 5): | ||
| return [] | ||
|
|
||
| def time_based_retrieval(self, user_id: str, limit: int = 10): | ||
| return [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class ContextBuilder: | ||
| def build(self, session, memories): | ||
| return {"session": session.session_id, "memories": memories} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class ModelFailover: | ||
| def pick(self, providers): | ||
| return providers[0] if providers else "fallback" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| class ModelRouter: | ||
| async def generate(self, prompt: str): | ||
| return { | ||
| "response": prompt, | ||
| "model": "openai", | ||
| "tokens": len(prompt.split()), | ||
| "cost": 0.0, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| class PromptBuilder: | ||
| def build(self, message: str, context: dict): | ||
| return f"{context}\n{message}" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from dataclasses import dataclass, field | ||
| from enum import Enum | ||
| from datetime import datetime, timezone | ||
|
|
||
|
|
||
| class SessionState(str, Enum): | ||
| CREATED = "CREATED" | ||
| ACTIVE = "ACTIVE" | ||
| IDLE = "IDLE" | ||
| PAUSED = "PAUSED" | ||
| CLOSED = "CLOSED" | ||
|
|
||
|
|
||
| @dataclass | ||
| class SessionContext: | ||
| session_id: str | ||
| user_id: str | ||
| channel: str | ||
| state: SessionState = SessionState.CREATED | ||
| message_history: list[dict] = field(default_factory=list) | ||
| tool_usage: list[dict] = field(default_factory=list) | ||
| context_window: list[str] = field(default_factory=list) | ||
| memory_references: list[str] = field(default_factory=list) | ||
| created_at: str = field( | ||
| default_factory=lambda: datetime.now(timezone.utc).isoformat() | ||
| ) | ||
| updated_at: str = field( | ||
| default_factory=lambda: datetime.now(timezone.utc).isoformat() | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: JSON extraction is fragile because it slices from the first
{to the last}in the whole response. If the model returns a valid JSON object plus any extra brace in trailing commentary, parsing fails and the model is incorrectly rejected. Parse the first valid JSON object instead of using a greedy substring. [logic error]Severity Level: Major⚠️
Steps of Reproduction ✅
Prompt for AI Agent 🤖