cluxion-agentplugin-preprocessing 0.3.0__tar.gz → 0.3.1__tar.gz
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.
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/PKG-INFO +4 -2
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/README.md +3 -1
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/pyproject.toml +1 -1
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_agentplugin_preprocessing/__init__.py +1 -1
- cluxion_agentplugin_preprocessing-0.3.1/src/cluxion_agentplugin_preprocessing/guard_watch.py +112 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_agentplugin_preprocessing/plugin.py +6 -1
- cluxion_agentplugin_preprocessing-0.3.1/tests/test_guard_watch.py +173 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/.github/profile/README.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/.gitignore +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/Docs/README.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/LICENSE +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/adapters/claude/.claude-plugin/plugin.json +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/adapters/claude/skills/preprocess/SKILL.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/adapters/codex/config-snippet.toml +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/cluxion-Docs/README.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/cluxion-Docs/architecture.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/cluxion-Docs/harness-logic.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/cluxion-Docs/honesty-preprocessing.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/cluxion-Docs/install-and-operations.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/cluxion-Docs/security.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/Cargo.lock +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/Cargo.toml +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/pyproject.toml +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/src/context.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/src/dispatch.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/src/guard.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/src/lib.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/src/main.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/src/queue.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/rust/cluxion_queue/src/types.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_agentplugin_preprocessing/cli.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_agentplugin_preprocessing/hermes_config.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_agentplugin_preprocessing/plugin.yaml +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_agentplugin_preprocessing/runner.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_agentplugin_preprocessing/schemas.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/__main__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/adapters/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/adapters/contract.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/adapters/grok_build.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/adapters/hermes.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/adapters/spec.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/bootstrap.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/cli.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/clarification.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/context_compress.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/dispatch_store.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/harness.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/intent.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/ledger.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/ledger_codec.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/plan_codec.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/preprocess.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/types.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/core/work_queue.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/guard_daemon_host.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/models/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/models/supervisor.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/models/vllm_mlx.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/resources/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/resources/guard_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/resources/py_queue.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/resources/queue_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/resources/rust_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/web/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/src/cluxion_runtime/web/browser_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_browser_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_clarification.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_cluxion_runtime_spine.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_context_compress.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_contract.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_dispatch_store.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_guard.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_ledger.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_queue_backends.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_runtime_adapter_cli.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_rust_queue.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/runtime/test_supervisor.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/test_bootstrap.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/test_hermes_config.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/test_packaging_policy.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/test_plugin.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/tests/test_runner.py +0 -0
{cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cluxion-agentplugin-preprocessing
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Universal agent plugin for Cluxion preprocessing, honesty contracts, clarification, Rust work queue, and resource-aware harness handoff.
|
|
5
5
|
Project-URL: Homepage, https://github.com/cluxion/cluxion-Agentplugin-preprocessing
|
|
6
6
|
Project-URL: Repository, https://github.com/cluxion/cluxion-Agentplugin-preprocessing
|
|
@@ -108,6 +108,8 @@ cluxion-runtime plan --surface hermes --prompt "작업 설명"
|
|
|
108
108
|
| `cluxion_context_compress` | context 사용률 초과 시 결정론적 압축 (pinned/recent 보존) |
|
|
109
109
|
| `cluxion_guard` | 실시간 리소스 guard (`status`/`start`/`stop`/`enforce`/`auto-enforce`) |
|
|
110
110
|
|
|
111
|
+
Hermes에서는 `on_session_start` hook이 guard daemon을 자동 시작합니다. `CLUXION_GUARD_AUTOSTART=0` 또는 `false`로 끌 수 있고, `post_tool_call` hook은 기본 30초마다 report-only `auto_enforce(dry_run=True)`를 실행합니다. 실제 적용은 `CLUXION_GUARD_AUTO_APPLY=1` 또는 `true`일 때만 켜집니다.
|
|
112
|
+
|
|
111
113
|
## 문서
|
|
112
114
|
|
|
113
115
|
- [Docs/README.md](Docs/README.md) — **처음 읽는 분** + 목차
|
|
@@ -120,4 +122,4 @@ cluxion-runtime plan --surface hermes --prompt "작업 설명"
|
|
|
120
122
|
|
|
121
123
|
## License
|
|
122
124
|
|
|
123
|
-
Apache-2.0
|
|
125
|
+
Apache-2.0
|
{cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/README.md
RENAMED
|
@@ -76,6 +76,8 @@ cluxion-runtime plan --surface hermes --prompt "작업 설명"
|
|
|
76
76
|
| `cluxion_context_compress` | context 사용률 초과 시 결정론적 압축 (pinned/recent 보존) |
|
|
77
77
|
| `cluxion_guard` | 실시간 리소스 guard (`status`/`start`/`stop`/`enforce`/`auto-enforce`) |
|
|
78
78
|
|
|
79
|
+
Hermes에서는 `on_session_start` hook이 guard daemon을 자동 시작합니다. `CLUXION_GUARD_AUTOSTART=0` 또는 `false`로 끌 수 있고, `post_tool_call` hook은 기본 30초마다 report-only `auto_enforce(dry_run=True)`를 실행합니다. 실제 적용은 `CLUXION_GUARD_AUTO_APPLY=1` 또는 `true`일 때만 켜집니다.
|
|
80
|
+
|
|
79
81
|
## 문서
|
|
80
82
|
|
|
81
83
|
- [Docs/README.md](Docs/README.md) — **처음 읽는 분** + 목차
|
|
@@ -88,4 +90,4 @@ cluxion-runtime plan --surface hermes --prompt "작업 설명"
|
|
|
88
90
|
|
|
89
91
|
## License
|
|
90
92
|
|
|
91
|
-
Apache-2.0
|
|
93
|
+
Apache-2.0
|
{cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cluxion-agentplugin-preprocessing"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Universal agent plugin for Cluxion preprocessing, honesty contracts, clarification, Rust work queue, and resource-aware harness handoff."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Hermes hook wiring for automatic guard daemon watch.
|
|
2
|
+
|
|
3
|
+
The hook surface is intentionally best-effort: failures are reported once to
|
|
4
|
+
stderr and never raised into the host agent.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from cluxion_runtime.resources import guard_bridge
|
|
16
|
+
|
|
17
|
+
AUTOSTART_ENV = "CLUXION_GUARD_AUTOSTART"
|
|
18
|
+
AUTO_APPLY_ENV = "CLUXION_GUARD_AUTO_APPLY"
|
|
19
|
+
WATCH_INTERVAL_ENV = "CLUXION_GUARD_WATCH_INTERVAL"
|
|
20
|
+
DEFAULT_WATCH_INTERVAL_SECONDS = 30.0
|
|
21
|
+
WARNING_INTERVAL_SECONDS = 300.0
|
|
22
|
+
|
|
23
|
+
_lock = threading.Lock()
|
|
24
|
+
_last_watch_at: float | None = None
|
|
25
|
+
_last_warning_at: float | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def on_session_start(**_: Any) -> None:
|
|
29
|
+
"""Start the guard daemon unless ``CLUXION_GUARD_AUTOSTART=0`` or ``false``.
|
|
30
|
+
|
|
31
|
+
Startup is idempotent: an already-running daemon is success. Hook failures
|
|
32
|
+
are logged as one concise stderr warning and never propagated to the host.
|
|
33
|
+
"""
|
|
34
|
+
if not _autostart_enabled():
|
|
35
|
+
return
|
|
36
|
+
try:
|
|
37
|
+
result = guard_bridge.start_daemon()
|
|
38
|
+
except Exception as exc:
|
|
39
|
+
_warn(f"cluxion guard autostart failed: {exc}")
|
|
40
|
+
return
|
|
41
|
+
if not result.get("ok", False):
|
|
42
|
+
reason = result.get("reason") or result.get("error") or "unknown"
|
|
43
|
+
_warn(f"cluxion guard autostart failed: {reason}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def post_tool_call(**_: Any) -> None:
|
|
47
|
+
"""Run a throttled guard watch after tool calls.
|
|
48
|
+
|
|
49
|
+
By default this is report-only and calls ``auto_enforce(..., dry_run=True)``.
|
|
50
|
+
Set ``CLUXION_GUARD_AUTO_APPLY=1`` or ``true`` to pass ``dry_run=False`` and
|
|
51
|
+
let the existing owned-only fail-closed enforcement path terminate
|
|
52
|
+
candidates.
|
|
53
|
+
"""
|
|
54
|
+
global _last_watch_at
|
|
55
|
+
|
|
56
|
+
now = time.monotonic()
|
|
57
|
+
with _lock:
|
|
58
|
+
if _last_watch_at is not None and now - _last_watch_at < _watch_interval_seconds():
|
|
59
|
+
return
|
|
60
|
+
_last_watch_at = now
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
result = guard_bridge.auto_enforce([os.getpid()], dry_run=not _auto_apply_enabled())
|
|
64
|
+
except Exception as exc:
|
|
65
|
+
_warn(f"cluxion guard watch failed: {exc}")
|
|
66
|
+
return
|
|
67
|
+
if not result.get("triggered", False) or not result.get("dry_run", True):
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
_warn_triggered(result, now)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _autostart_enabled() -> bool:
|
|
74
|
+
return os.environ.get(AUTOSTART_ENV, "1").strip().lower() not in {"0", "false"}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _auto_apply_enabled() -> bool:
|
|
78
|
+
return os.environ.get(AUTO_APPLY_ENV, "").strip().lower() in {"1", "true"}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _watch_interval_seconds() -> float:
|
|
82
|
+
raw = os.environ.get(WATCH_INTERVAL_ENV)
|
|
83
|
+
if raw is None:
|
|
84
|
+
return DEFAULT_WATCH_INTERVAL_SECONDS
|
|
85
|
+
try:
|
|
86
|
+
return max(0.0, float(raw))
|
|
87
|
+
except ValueError:
|
|
88
|
+
return DEFAULT_WATCH_INTERVAL_SECONDS
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _warn_triggered(result: dict[str, Any], now: float) -> None:
|
|
92
|
+
global _last_warning_at
|
|
93
|
+
|
|
94
|
+
with _lock:
|
|
95
|
+
if _last_warning_at is not None and now - _last_warning_at < WARNING_INTERVAL_SECONDS:
|
|
96
|
+
return
|
|
97
|
+
_last_warning_at = now
|
|
98
|
+
|
|
99
|
+
pids = [str(entry.get("pid")) for entry in result.get("candidates", []) if isinstance(entry, dict)]
|
|
100
|
+
reasons = [str(reason) for reason in result.get("trigger_reasons", [])]
|
|
101
|
+
_warn(
|
|
102
|
+
"cluxion guard triggered"
|
|
103
|
+
f" candidates={','.join(pids) if pids else 'none'}"
|
|
104
|
+
f" reasons={'; '.join(reasons) if reasons else 'unknown'}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _warn(message: str) -> None:
|
|
109
|
+
print(message, file=sys.stderr)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = ["on_session_start", "post_tool_call"]
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from cluxion_agentplugin_preprocessing import runner
|
|
8
|
+
from cluxion_agentplugin_preprocessing import guard_watch, runner
|
|
9
9
|
from cluxion_agentplugin_preprocessing.schemas import (
|
|
10
10
|
BOOTSTRAP_SCHEMA,
|
|
11
11
|
BROWSER_CLICK_SCHEMA,
|
|
@@ -30,6 +30,11 @@ if TYPE_CHECKING:
|
|
|
30
30
|
|
|
31
31
|
def register(ctx: object) -> None:
|
|
32
32
|
"""Register Cluxion preprocessing tools with the host agent."""
|
|
33
|
+
register_hook = getattr(ctx, "register_hook", None)
|
|
34
|
+
if callable(register_hook):
|
|
35
|
+
register_hook("on_session_start", guard_watch.on_session_start)
|
|
36
|
+
register_hook("post_tool_call", guard_watch.post_tool_call)
|
|
37
|
+
|
|
33
38
|
ctx.register_tool(
|
|
34
39
|
name="cluxion_plan",
|
|
35
40
|
toolset="cluxion",
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from cluxion_agentplugin_preprocessing import guard_watch, plugin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def reset_guard_watch(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
12
|
+
monkeypatch.setattr(guard_watch, "_last_watch_at", None)
|
|
13
|
+
monkeypatch.setattr(guard_watch, "_last_warning_at", None)
|
|
14
|
+
monkeypatch.delenv(guard_watch.AUTOSTART_ENV, raising=False)
|
|
15
|
+
monkeypatch.delenv(guard_watch.AUTO_APPLY_ENV, raising=False)
|
|
16
|
+
monkeypatch.delenv(guard_watch.WATCH_INTERVAL_ENV, raising=False)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HookContext:
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self.hooks: dict[str, object] = {}
|
|
22
|
+
self.tools: dict[str, object] = {}
|
|
23
|
+
|
|
24
|
+
def register_hook(self, name: str, handler: object) -> None:
|
|
25
|
+
self.hooks[name] = handler
|
|
26
|
+
|
|
27
|
+
def register_tool(self, *, name: str, **kwargs: object) -> None:
|
|
28
|
+
self.tools[name] = kwargs
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ToolOnlyContext:
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
self.tools: dict[str, object] = {}
|
|
34
|
+
|
|
35
|
+
def register_tool(self, *, name: str, **kwargs: object) -> None:
|
|
36
|
+
self.tools[name] = kwargs
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_register_adds_guard_hooks_when_supported() -> None:
|
|
40
|
+
ctx = HookContext()
|
|
41
|
+
|
|
42
|
+
plugin.register(ctx)
|
|
43
|
+
|
|
44
|
+
assert ctx.hooks == {
|
|
45
|
+
"on_session_start": guard_watch.on_session_start,
|
|
46
|
+
"post_tool_call": guard_watch.post_tool_call,
|
|
47
|
+
}
|
|
48
|
+
assert "cluxion_guard" in ctx.tools
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_register_tolerates_context_without_hooks() -> None:
|
|
52
|
+
ctx = ToolOnlyContext()
|
|
53
|
+
|
|
54
|
+
plugin.register(ctx)
|
|
55
|
+
|
|
56
|
+
assert "cluxion_guard" in ctx.tools
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_autostart_default_on(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
60
|
+
calls = 0
|
|
61
|
+
|
|
62
|
+
def fake_start_daemon() -> dict[str, object]:
|
|
63
|
+
nonlocal calls
|
|
64
|
+
calls += 1
|
|
65
|
+
return {"ok": True, "started": False, "reason": "already_running"}
|
|
66
|
+
|
|
67
|
+
monkeypatch.setattr(guard_watch.guard_bridge, "start_daemon", fake_start_daemon)
|
|
68
|
+
|
|
69
|
+
guard_watch.on_session_start(session_id="s1", telemetry_schema_version=1)
|
|
70
|
+
|
|
71
|
+
assert calls == 1
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.parametrize("value", ["0", "false", "False"])
|
|
75
|
+
def test_autostart_env_gate_off(monkeypatch: pytest.MonkeyPatch, value: str) -> None:
|
|
76
|
+
calls = 0
|
|
77
|
+
|
|
78
|
+
def fake_start_daemon() -> dict[str, object]:
|
|
79
|
+
nonlocal calls
|
|
80
|
+
calls += 1
|
|
81
|
+
return {"ok": True}
|
|
82
|
+
|
|
83
|
+
monkeypatch.setenv(guard_watch.AUTOSTART_ENV, value)
|
|
84
|
+
monkeypatch.setattr(guard_watch.guard_bridge, "start_daemon", fake_start_daemon)
|
|
85
|
+
|
|
86
|
+
guard_watch.on_session_start(session_id="s1", telemetry_schema_version=1)
|
|
87
|
+
|
|
88
|
+
assert calls == 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_autostart_exception_is_swallowed_with_warning(
|
|
92
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
93
|
+
) -> None:
|
|
94
|
+
def fake_start_daemon() -> dict[str, object]:
|
|
95
|
+
raise RuntimeError("boom")
|
|
96
|
+
|
|
97
|
+
monkeypatch.setattr(guard_watch.guard_bridge, "start_daemon", fake_start_daemon)
|
|
98
|
+
|
|
99
|
+
guard_watch.on_session_start(session_id="s1", telemetry_schema_version=1)
|
|
100
|
+
|
|
101
|
+
captured = capsys.readouterr()
|
|
102
|
+
assert captured.out == ""
|
|
103
|
+
assert "cluxion guard autostart failed: boom" in captured.err
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_watch_throttle(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
107
|
+
now = 100.0
|
|
108
|
+
calls: list[dict[str, Any]] = []
|
|
109
|
+
|
|
110
|
+
def fake_monotonic() -> float:
|
|
111
|
+
return now
|
|
112
|
+
|
|
113
|
+
def fake_auto_enforce(owned_roots: list[int], *, dry_run: bool) -> dict[str, object]:
|
|
114
|
+
calls.append({"owned_roots": owned_roots, "dry_run": dry_run})
|
|
115
|
+
return {"ok": True, "triggered": False, "dry_run": dry_run}
|
|
116
|
+
|
|
117
|
+
monkeypatch.setattr(guard_watch.time, "monotonic", fake_monotonic)
|
|
118
|
+
monkeypatch.setattr(guard_watch.guard_bridge, "auto_enforce", fake_auto_enforce)
|
|
119
|
+
|
|
120
|
+
guard_watch.post_tool_call(tool_name="terminal", telemetry_schema_version=1)
|
|
121
|
+
guard_watch.post_tool_call(tool_name="terminal", telemetry_schema_version=1)
|
|
122
|
+
now = 131.0
|
|
123
|
+
guard_watch.post_tool_call(tool_name="terminal", telemetry_schema_version=1)
|
|
124
|
+
|
|
125
|
+
assert len(calls) == 2
|
|
126
|
+
assert calls[0]["dry_run"] is True
|
|
127
|
+
assert calls[1]["dry_run"] is True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_triggered_dry_run_warning_is_rate_limited(
|
|
131
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
132
|
+
) -> None:
|
|
133
|
+
now = 100.0
|
|
134
|
+
|
|
135
|
+
def fake_monotonic() -> float:
|
|
136
|
+
return now
|
|
137
|
+
|
|
138
|
+
def fake_auto_enforce(owned_roots: list[int], *, dry_run: bool) -> dict[str, object]:
|
|
139
|
+
return {
|
|
140
|
+
"ok": True,
|
|
141
|
+
"triggered": True,
|
|
142
|
+
"dry_run": dry_run,
|
|
143
|
+
"candidates": [{"pid": 123}, {"pid": 456}],
|
|
144
|
+
"trigger_reasons": ["cpu_avg 99.0 >= sustained_cpu 85.0"],
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
monkeypatch.setenv(guard_watch.WATCH_INTERVAL_ENV, "0")
|
|
148
|
+
monkeypatch.setattr(guard_watch.time, "monotonic", fake_monotonic)
|
|
149
|
+
monkeypatch.setattr(guard_watch.guard_bridge, "auto_enforce", fake_auto_enforce)
|
|
150
|
+
|
|
151
|
+
guard_watch.post_tool_call(tool_name="terminal", telemetry_schema_version=1)
|
|
152
|
+
guard_watch.post_tool_call(tool_name="terminal", telemetry_schema_version=1)
|
|
153
|
+
|
|
154
|
+
captured = capsys.readouterr()
|
|
155
|
+
assert captured.out == ""
|
|
156
|
+
assert captured.err.count("cluxion guard triggered") == 1
|
|
157
|
+
assert "candidates=123,456" in captured.err
|
|
158
|
+
assert "cpu_avg 99.0 >= sustained_cpu 85.0" in captured.err
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_auto_apply_passes_dry_run_false(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
162
|
+
calls: list[dict[str, Any]] = []
|
|
163
|
+
|
|
164
|
+
def fake_auto_enforce(owned_roots: list[int], *, dry_run: bool) -> dict[str, object]:
|
|
165
|
+
calls.append({"owned_roots": owned_roots, "dry_run": dry_run})
|
|
166
|
+
return {"ok": True, "triggered": False, "dry_run": dry_run}
|
|
167
|
+
|
|
168
|
+
monkeypatch.setenv(guard_watch.AUTO_APPLY_ENV, "true")
|
|
169
|
+
monkeypatch.setattr(guard_watch.guard_bridge, "auto_enforce", fake_auto_enforce)
|
|
170
|
+
|
|
171
|
+
guard_watch.post_tool_call(session_id="s1", telemetry_schema_version=1)
|
|
172
|
+
|
|
173
|
+
assert calls == [{"owned_roots": [guard_watch.os.getpid()], "dry_run": False}]
|
|
File without changes
|
{cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/.gitignore
RENAMED
|
File without changes
|
{cluxion_agentplugin_preprocessing-0.3.0 → cluxion_agentplugin_preprocessing-0.3.1}/Docs/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|