mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 16:01:49 +08:00
Compare commits
2 Commits
fix/plugin
...
hermes/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c74030a11d | ||
|
|
94a1990404 |
@@ -543,8 +543,7 @@ Hermes runs on Linux, macOS, and Windows. When writing code that touches the OS:
|
|||||||
3. **Process management.** `os.setsid()`, `os.killpg()`, and signal handling differ on Windows. Use platform checks:
|
3. **Process management.** `os.setsid()`, `os.killpg()`, and signal handling differ on Windows. Use platform checks:
|
||||||
```python
|
```python
|
||||||
import platform
|
import platform
|
||||||
if platform.system() != "Windows":
|
kwargs["start_new_session"] = platform.system() != "Windows"
|
||||||
kwargs["preexec_fn"] = os.setsid
|
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Path separators.** Use `pathlib.Path` instead of string concatenation with `/`.
|
4. **Path separators.** Use `pathlib.Path` instead of string concatenation with `/`.
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ class WhatsAppAdapter(BasePlatformAdapter):
|
|||||||
],
|
],
|
||||||
stdout=bridge_log_fh,
|
stdout=bridge_log_fh,
|
||||||
stderr=bridge_log_fh,
|
stderr=bridge_log_fh,
|
||||||
preexec_fn=None if _IS_WINDOWS else os.setsid,
|
start_new_session=not _IS_WINDOWS,
|
||||||
env=bridge_env,
|
env=bridge_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ GUARDED_FILES = [
|
|||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
|
|
||||||
|
|
||||||
def _get_preexec_fn_values(filepath: Path) -> list:
|
def _get_kwarg_values(filepath: Path, kwarg_name: str) -> list:
|
||||||
"""Find all preexec_fn= keyword arguments in Popen calls."""
|
"""Find all ``kwarg_name=...`` keyword arguments in function calls."""
|
||||||
source = filepath.read_text(encoding="utf-8")
|
source = filepath.read_text(encoding="utf-8")
|
||||||
tree = ast.parse(source, filename=str(filepath))
|
tree = ast.parse(source, filename=str(filepath))
|
||||||
values = []
|
values = []
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
if isinstance(node, ast.keyword) and node.arg == "preexec_fn":
|
if isinstance(node, ast.keyword) and node.arg == kwarg_name:
|
||||||
values.append(ast.dump(node.value))
|
values.append(ast.dump(node.value))
|
||||||
return values
|
return values
|
||||||
|
|
||||||
@@ -38,13 +38,33 @@ class TestNoUnconditionalSetsid:
|
|||||||
filepath = PROJECT_ROOT / relpath
|
filepath = PROJECT_ROOT / relpath
|
||||||
if not filepath.exists():
|
if not filepath.exists():
|
||||||
pytest.skip(f"{relpath} not found")
|
pytest.skip(f"{relpath} not found")
|
||||||
values = _get_preexec_fn_values(filepath)
|
values = _get_kwarg_values(filepath, "preexec_fn")
|
||||||
for val in values:
|
for val in values:
|
||||||
# A bare os.setsid would be: Attribute(value=Name(id='os'), attr='setsid')
|
# A bare os.setsid would be: Attribute(value=Name(id='os'), attr='setsid')
|
||||||
assert "attr='setsid'" not in val or "IfExp" in val or "None" in val, (
|
assert "attr='setsid'" not in val or "IfExp" in val or "None" in val, (
|
||||||
f"{relpath} has unconditional preexec_fn=os.setsid"
|
f"{relpath} has unconditional preexec_fn=os.setsid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("relpath", GUARDED_FILES)
|
||||||
|
def test_start_new_session_is_guarded(self, relpath):
|
||||||
|
"""start_new_session must not be an unconditional True.
|
||||||
|
|
||||||
|
The modern thread-safe replacement for ``preexec_fn=os.setsid`` is
|
||||||
|
``start_new_session=<bool>``, but Windows' subprocess backend doesn't
|
||||||
|
support it — so it must always be gated on a platform check (typically
|
||||||
|
``not _IS_WINDOWS``), never a bare ``True``.
|
||||||
|
"""
|
||||||
|
filepath = PROJECT_ROOT / relpath
|
||||||
|
if not filepath.exists():
|
||||||
|
pytest.skip(f"{relpath} not found")
|
||||||
|
values = _get_kwarg_values(filepath, "start_new_session")
|
||||||
|
for val in values:
|
||||||
|
# A bare True literal would be: Constant(value=True)
|
||||||
|
assert val != "Constant(value=True)", (
|
||||||
|
f"{relpath} has unconditional start_new_session=True "
|
||||||
|
f"(must be guarded, e.g. `not _IS_WINDOWS`)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestIsWindowsConstant:
|
class TestIsWindowsConstant:
|
||||||
"""Each guarded file must define _IS_WINDOWS."""
|
"""Each guarded file must define _IS_WINDOWS."""
|
||||||
|
|||||||
@@ -1065,7 +1065,7 @@ def execute_code(
|
|||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
stdin=subprocess.DEVNULL,
|
stdin=subprocess.DEVNULL,
|
||||||
preexec_fn=None if _IS_WINDOWS else os.setsid,
|
start_new_session=not _IS_WINDOWS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Poll loop: watch for exit, timeout, and interrupt ---
|
# --- Poll loop: watch for exit, timeout, and interrupt ---
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def _popen_bash(
|
|||||||
"""Spawn a subprocess with standard stdout/stderr/stdin setup.
|
"""Spawn a subprocess with standard stdout/stderr/stdin setup.
|
||||||
|
|
||||||
If *stdin_data* is provided, writes it asynchronously via :func:`_pipe_stdin`.
|
If *stdin_data* is provided, writes it asynchronously via :func:`_pipe_stdin`.
|
||||||
Backends with special Popen needs (e.g. local's ``preexec_fn``) can bypass
|
Backends with special Popen needs (e.g. local's ``start_new_session``) can bypass
|
||||||
this and call :func:`_pipe_stdin` directly.
|
this and call :func:`_pipe_stdin` directly.
|
||||||
"""
|
"""
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ class LocalEnvironment(BaseEnvironment):
|
|||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
stdin=subprocess.PIPE if stdin_data is not None else subprocess.DEVNULL,
|
stdin=subprocess.PIPE if stdin_data is not None else subprocess.DEVNULL,
|
||||||
preexec_fn=None if _IS_WINDOWS else os.setsid,
|
start_new_session=not _IS_WINDOWS,
|
||||||
)
|
)
|
||||||
|
|
||||||
if stdin_data is not None:
|
if stdin_data is not None:
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ class ProcessRegistry:
|
|||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
preexec_fn=None if _IS_WINDOWS else os.setsid,
|
start_new_session=not _IS_WINDOWS,
|
||||||
)
|
)
|
||||||
|
|
||||||
session.process = proc
|
session.process = proc
|
||||||
|
|||||||
@@ -129,8 +129,7 @@ except UnicodeDecodeError:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
import platform
|
import platform
|
||||||
if platform.system() != "Windows":
|
kwargs["start_new_session"] = platform.system() != "Windows"
|
||||||
kwargs["preexec_fn"] = os.setsid
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Path separators
|
### 4. Path separators
|
||||||
|
|||||||
Reference in New Issue
Block a user