cluxion-agentplugin-preprocessing 0.3.17__tar.gz → 0.3.18__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.17 → cluxion_agentplugin_preprocessing-0.3.18}/PKG-INFO +1 -1
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/pyproject.toml +1 -1
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/guard_watch.py +7 -1
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/plugin.py +1 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/plugin.yaml +12 -3
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/runner.py +14 -1
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/cli.py +18 -3
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/llm_compress.py +18 -1
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/guard_daemon_host.py +44 -6
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/resources/queue_bridge.py +61 -4
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_context_compress.py +53 -1
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_context_compress_llm_forget.py +45 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_guard_daemon_host.py +26 -3
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/test_guard_watch.py +1 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/test_runner.py +8 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/.github/profile/README.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/.gitignore +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/Docs/README.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/LICENSE +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/README.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/adapters/claude/.claude-plugin/plugin.json +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/adapters/claude/skills/preprocess/SKILL.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/adapters/codex/config-snippet.toml +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/cluxion-Docs/README.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/cluxion-Docs/architecture.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/cluxion-Docs/harness-logic.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/cluxion-Docs/honesty-preprocessing.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/cluxion-Docs/install-and-operations.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/cluxion-Docs/security.md +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/Cargo.lock +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/Cargo.toml +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/pyproject.toml +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/src/context.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/src/dispatch.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/src/guard.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/src/lib.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/src/main.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/src/queue.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/rust/cluxion_queue/src/types.rs +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/cli.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/doctor/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/doctor/catalog.json +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/doctor/framework.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/doctor/probes.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/hermes_config.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_agentplugin_preprocessing/schemas.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/__main__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/adapters/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/adapters/contract.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/adapters/grok_build.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/adapters/hermes.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/adapters/spec.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/bootstrap.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/clarification.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/context_compress.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/dispatch_store.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/harness.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/hybrid_forget.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/intent.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/ledger.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/ledger_codec.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/plan_codec.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/preprocess.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/types.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/core/work_queue.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/models/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/models/supervisor.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/models/vllm_mlx.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/resources/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/resources/guard_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/resources/py_queue.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/resources/rust_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/web/__init__.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/src/cluxion_runtime/web/browser_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_auto_compress_middleware.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_browser_bridge.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_clarification.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_cluxion_runtime_spine.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_contract.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_dispatch_store.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_guard.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_ledger.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_py_queue_concurrency.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_queue_backends.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_runtime_adapter_cli.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_rust_queue.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/runtime/test_supervisor.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/test_bootstrap.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/test_doctor.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/test_hermes_config.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/test_packaging_policy.py +0 -0
- {cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/tests/test_plugin.py +0 -0
{cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/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.18
|
|
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
|
{cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/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.18"
|
|
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"
|
|
@@ -26,6 +26,12 @@ _last_watch_at: float | None = None
|
|
|
26
26
|
_last_warning_at: float | None = None
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def on_session_end(**_: Any) -> None:
|
|
30
|
+
"""Stop the guard daemon on clean session end so orphans do not linger."""
|
|
31
|
+
with contextlib.suppress(Exception):
|
|
32
|
+
guard_bridge.stop_daemon()
|
|
33
|
+
|
|
34
|
+
|
|
29
35
|
def on_session_start(**_: Any) -> None:
|
|
30
36
|
"""Start the guard daemon unless ``CLUXION_GUARD_AUTOSTART=0`` or ``false``.
|
|
31
37
|
|
|
@@ -111,4 +117,4 @@ def _warn(message: str) -> None:
|
|
|
111
117
|
print(message, file=sys.stderr)
|
|
112
118
|
|
|
113
119
|
|
|
114
|
-
__all__ = ["on_session_start", "post_tool_call"]
|
|
120
|
+
__all__ = ["on_session_end", "on_session_start", "post_tool_call"]
|
|
@@ -43,6 +43,7 @@ def register(ctx: object) -> None:
|
|
|
43
43
|
register_hook = getattr(ctx, "register_hook", None)
|
|
44
44
|
if callable(register_hook):
|
|
45
45
|
register_hook("on_session_start", guard_watch.on_session_start)
|
|
46
|
+
register_hook("on_session_end", guard_watch.on_session_end)
|
|
46
47
|
register_hook("post_tool_call", guard_watch.post_tool_call)
|
|
47
48
|
|
|
48
49
|
register_mw = getattr(ctx, "register_middleware", None)
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
name:
|
|
2
|
-
version: 0.
|
|
1
|
+
name: cluxion-agentplugin-preprocessing
|
|
2
|
+
version: 0.3.18
|
|
3
3
|
description: "Universal agent preprocessing plugin: honesty contracts, clarification, Rust work queue, resource-aware harness handoff. Connected AI calls cluxion tools; plugin does not own models."
|
|
4
4
|
author: cluxion
|
|
5
5
|
kind: standalone
|
|
6
6
|
provides_tools:
|
|
7
|
-
- cluxion_bootstrap
|
|
8
7
|
- cluxion_plan
|
|
8
|
+
- cluxion_clarify
|
|
9
|
+
- cluxion_bootstrap
|
|
9
10
|
- cluxion_serve_local
|
|
10
11
|
- cluxion_hermes_config
|
|
11
12
|
- cluxion_queue_next
|
|
12
13
|
- cluxion_queue_record
|
|
13
14
|
- cluxion_queue_brief
|
|
15
|
+
- cluxion_context_compress
|
|
16
|
+
- cluxion_guard
|
|
17
|
+
- cluxion_web_search
|
|
18
|
+
- cluxion_browser_open
|
|
19
|
+
- cluxion_browser_extract
|
|
20
|
+
- cluxion_browser_click
|
|
21
|
+
- cluxion_browser_type
|
|
22
|
+
- cluxion_doctor
|
|
@@ -247,10 +247,23 @@ def _execute_json(command: Sequence[str], stdin: str | None, command_runner: Com
|
|
|
247
247
|
except OSError as exc:
|
|
248
248
|
return RuntimeResult(False, tuple(command), {"error": str(exc)})
|
|
249
249
|
if completed.returncode != 0:
|
|
250
|
+
error = "cluxion-runtime failed"
|
|
251
|
+
stderr = completed.stderr.strip()
|
|
252
|
+
stdout = completed.stdout.strip()
|
|
253
|
+
for payload_text in (stderr, stdout):
|
|
254
|
+
if not payload_text:
|
|
255
|
+
continue
|
|
256
|
+
try:
|
|
257
|
+
parsed_err = json.loads(payload_text)
|
|
258
|
+
except json.JSONDecodeError:
|
|
259
|
+
continue
|
|
260
|
+
if isinstance(parsed_err, dict) and parsed_err.get("error"):
|
|
261
|
+
error = str(parsed_err["error"])
|
|
262
|
+
break
|
|
250
263
|
return RuntimeResult(
|
|
251
264
|
False,
|
|
252
265
|
tuple(command),
|
|
253
|
-
{"error":
|
|
266
|
+
{"error": error, "stderr": stderr, "returncode": completed.returncode},
|
|
254
267
|
)
|
|
255
268
|
try:
|
|
256
269
|
parsed = json.loads(completed.stdout)
|
|
@@ -206,11 +206,27 @@ def _run_context_compress(_: argparse.Namespace) -> int:
|
|
|
206
206
|
return 0
|
|
207
207
|
|
|
208
208
|
|
|
209
|
+
_VALID_GUARD_ACTIONS = frozenset({"status", "start", "stop", "enforce", "auto-enforce"})
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _guard_action_error(action: str) -> str:
|
|
213
|
+
return (
|
|
214
|
+
f"unknown guard action: {action} "
|
|
215
|
+
"(expected: status|start|stop|enforce|auto-enforce)"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
209
219
|
def _run_guard(_: argparse.Namespace) -> int:
|
|
210
220
|
from cluxion_runtime.resources import guard_bridge
|
|
211
221
|
|
|
212
222
|
payload = _payload_from_stdin()
|
|
213
223
|
action = str(payload.get("action", "status"))
|
|
224
|
+
if action not in _VALID_GUARD_ACTIONS:
|
|
225
|
+
print(
|
|
226
|
+
json.dumps({"ok": False, "error": _guard_action_error(action)}, ensure_ascii=False, sort_keys=True),
|
|
227
|
+
file=sys.stderr,
|
|
228
|
+
)
|
|
229
|
+
return 1
|
|
214
230
|
try:
|
|
215
231
|
if action == "start":
|
|
216
232
|
result = guard_bridge.start_daemon(
|
|
@@ -256,9 +272,8 @@ def _run_guard(_: argparse.Namespace) -> int:
|
|
|
256
272
|
result["scan"] = guard_bridge.scan([int(pid) for pid in owned_roots])
|
|
257
273
|
else:
|
|
258
274
|
print(
|
|
259
|
-
json.dumps(
|
|
260
|
-
|
|
261
|
-
)
|
|
275
|
+
json.dumps({"ok": False, "error": _guard_action_error(action)}, ensure_ascii=False, sort_keys=True),
|
|
276
|
+
file=sys.stderr,
|
|
262
277
|
)
|
|
263
278
|
return 1
|
|
264
279
|
except RuntimeError as exc:
|
|
@@ -35,13 +35,15 @@ _SUMMARY_INSTRUCTIONS = (
|
|
|
35
35
|
|
|
36
36
|
_HARD_TOKEN_RE = re.compile(
|
|
37
37
|
r"\b(?:"
|
|
38
|
-
r"\d+(
|
|
38
|
+
r"\d+(?:[._]\d+)+(?:k|m|만|억)?"
|
|
39
|
+
r"|\d+(?:\.\d+)?(?:k|m|만|억)?"
|
|
39
40
|
r"|[A-Za-z][\w.-]*\d[\w.-]*"
|
|
40
41
|
r"|\d[\w.-]+"
|
|
41
42
|
r")\b",
|
|
42
43
|
re.IGNORECASE,
|
|
43
44
|
)
|
|
44
45
|
_NUMERIC_SUFFIX_RE = re.compile(r"^(\d+(?:\.\d+)?)(k|m|만|억)?$", re.IGNORECASE)
|
|
46
|
+
_MULTI_GROUP_NUMERIC_RE = re.compile(r"^\d+(?:[._]\d+)+$")
|
|
45
47
|
_STRIP_LABEL_PREFIX_RE = re.compile(r"(?:\w+:\s*)+", re.IGNORECASE)
|
|
46
48
|
_SUFFIX_MULTIPLIERS = {"k": 1000, "m": 1_000_000, "만": 10_000, "억": 100_000_000}
|
|
47
49
|
|
|
@@ -129,10 +131,25 @@ def _numeric_variants(token: str) -> set[str]:
|
|
|
129
131
|
return variants
|
|
130
132
|
|
|
131
133
|
|
|
134
|
+
def _bounded_numeric_in_source(norm_token: str, norm_source: str) -> bool:
|
|
135
|
+
"""Match a dotted/underscored numeric token only as a complete identifier."""
|
|
136
|
+
pattern = rf"(?<![\d._]){re.escape(norm_token)}(?![\d._])"
|
|
137
|
+
return re.search(pattern, norm_source) is not None
|
|
138
|
+
|
|
139
|
+
|
|
132
140
|
def _token_traceable_in_source(token: str, source: str) -> bool:
|
|
133
141
|
norm_source = _normalize_for_match(source)
|
|
134
142
|
norm_token = _normalize_for_match(token)
|
|
135
143
|
|
|
144
|
+
# Version/IP/port-like tokens must match atomically — never as a substring prefix
|
|
145
|
+
# of a longer value (e.g. 0.3.1 must not trace to source 0.3.17).
|
|
146
|
+
if _MULTI_GROUP_NUMERIC_RE.match(norm_token):
|
|
147
|
+
if _bounded_numeric_in_source(norm_token, norm_source):
|
|
148
|
+
return True
|
|
149
|
+
return any(
|
|
150
|
+
_bounded_numeric_in_source(variant, norm_source) for variant in _numeric_variants(token)
|
|
151
|
+
)
|
|
152
|
+
|
|
136
153
|
if norm_token in norm_source:
|
|
137
154
|
return True
|
|
138
155
|
|
|
@@ -15,8 +15,9 @@ import psutil
|
|
|
15
15
|
STATE_FILE_NAME = "guard_state.json"
|
|
16
16
|
HEARTBEAT_FILE_NAME = "guard_heartbeat"
|
|
17
17
|
PID_FILE_NAME = "guard_daemon.pid"
|
|
18
|
-
DEFAULT_IDLE_TTL_MS =
|
|
18
|
+
DEFAULT_IDLE_TTL_MS = 120_000
|
|
19
19
|
PROC_SCAN_EVERY_N_TICKS = 5
|
|
20
|
+
STATE_WRITE_EVERY_N_TICKS = 30
|
|
20
21
|
_MAX_REPORTED_PIDS = 50
|
|
21
22
|
_MIN_INTERVAL_MS = 100
|
|
22
23
|
_MIN_WINDOW = 1
|
|
@@ -145,6 +146,35 @@ def _write_state_atomically(store_dir: Path, state: dict[str, Any]) -> None:
|
|
|
145
146
|
os.replace(tmp_path, state_path)
|
|
146
147
|
|
|
147
148
|
|
|
149
|
+
def _state_write_fingerprint(state: dict[str, Any]) -> str:
|
|
150
|
+
"""Stable snapshot key excluding volatile timestamps."""
|
|
151
|
+
current = dict(state.get("current", {}))
|
|
152
|
+
current.pop("sampled_at_ms", None)
|
|
153
|
+
window = state.get("window")
|
|
154
|
+
payload = {
|
|
155
|
+
"current": current,
|
|
156
|
+
"window": window,
|
|
157
|
+
"interval_ms": state.get("interval_ms"),
|
|
158
|
+
}
|
|
159
|
+
return json.dumps(payload, separators=(",", ":"), sort_keys=True)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _write_state_if_changed(
|
|
163
|
+
store_dir: Path,
|
|
164
|
+
state: dict[str, Any],
|
|
165
|
+
*,
|
|
166
|
+
last_fingerprint: str | None,
|
|
167
|
+
tick: int,
|
|
168
|
+
) -> str:
|
|
169
|
+
fingerprint = _state_write_fingerprint(state)
|
|
170
|
+
if (
|
|
171
|
+
fingerprint != last_fingerprint
|
|
172
|
+
or tick % STATE_WRITE_EVERY_N_TICKS == 0
|
|
173
|
+
):
|
|
174
|
+
_write_state_atomically(store_dir, state)
|
|
175
|
+
return fingerprint
|
|
176
|
+
|
|
177
|
+
|
|
148
178
|
def _idle_ttl_ms() -> int:
|
|
149
179
|
raw = os.environ.get("CLUXION_GUARD_IDLE_TTL_MS")
|
|
150
180
|
if raw is None:
|
|
@@ -192,10 +222,11 @@ def _daemon_loop_step(
|
|
|
192
222
|
interval_ms: int,
|
|
193
223
|
tick: int,
|
|
194
224
|
idle_ttl_ms: int,
|
|
225
|
+
last_fingerprint: str | None = None,
|
|
195
226
|
now_ms: int | None = None,
|
|
196
|
-
) -> tuple[bool, ProcessScanCache]:
|
|
227
|
+
) -> tuple[bool, ProcessScanCache, str]:
|
|
197
228
|
if _check_idle_exit(base, idle_ttl_ms, now_ms=now_ms):
|
|
198
|
-
return False, process_cache
|
|
229
|
+
return False, process_cache, last_fingerprint or ""
|
|
199
230
|
state, process_cache = _python_daemon_tick(
|
|
200
231
|
process_cache,
|
|
201
232
|
cpu_window,
|
|
@@ -204,8 +235,13 @@ def _daemon_loop_step(
|
|
|
204
235
|
interval_ms,
|
|
205
236
|
tick,
|
|
206
237
|
)
|
|
207
|
-
|
|
208
|
-
|
|
238
|
+
fingerprint = _write_state_if_changed(
|
|
239
|
+
base,
|
|
240
|
+
state,
|
|
241
|
+
last_fingerprint=last_fingerprint,
|
|
242
|
+
tick=tick,
|
|
243
|
+
)
|
|
244
|
+
return True, process_cache, fingerprint
|
|
209
245
|
|
|
210
246
|
|
|
211
247
|
def _run_python_daemon(store_dir: str, interval_ms: int, window: int) -> None:
|
|
@@ -219,9 +255,10 @@ def _run_python_daemon(store_dir: str, interval_ms: int, window: int) -> None:
|
|
|
219
255
|
cpu_window: list[float] = []
|
|
220
256
|
ram_window: list[int] = []
|
|
221
257
|
tick = 0
|
|
258
|
+
last_fingerprint: str | None = None
|
|
222
259
|
|
|
223
260
|
while True:
|
|
224
|
-
keep_running, process_cache = _daemon_loop_step(
|
|
261
|
+
keep_running, process_cache, last_fingerprint = _daemon_loop_step(
|
|
225
262
|
base,
|
|
226
263
|
process_cache=process_cache,
|
|
227
264
|
cpu_window=cpu_window,
|
|
@@ -230,6 +267,7 @@ def _run_python_daemon(store_dir: str, interval_ms: int, window: int) -> None:
|
|
|
230
267
|
interval_ms=interval_ms,
|
|
231
268
|
tick=tick,
|
|
232
269
|
idle_ttl_ms=idle_ttl_ms,
|
|
270
|
+
last_fingerprint=last_fingerprint,
|
|
233
271
|
)
|
|
234
272
|
if not keep_running:
|
|
235
273
|
return
|
|
@@ -114,15 +114,72 @@ def queue_status(*, store_dir: Path | None = None) -> dict[str, object]:
|
|
|
114
114
|
|
|
115
115
|
def compress_context(payload: Mapping[str, object]) -> dict[str, object]:
|
|
116
116
|
"""Deterministic context compression — pure function, no store_dir involved."""
|
|
117
|
+
from cluxion_runtime.core import context_compress
|
|
118
|
+
|
|
117
119
|
body = dict(payload)
|
|
118
120
|
backend = resolve_backend()
|
|
121
|
+
if backend == "python":
|
|
122
|
+
return context_compress.compress(body)
|
|
119
123
|
if backend == "native":
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
stage1 = _invoke_native("context-compress", body)
|
|
125
|
+
else:
|
|
126
|
+
stage1 = _invoke_subprocess("context-compress", body)
|
|
127
|
+
return _finalize_context_compress(body, stage1)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _context_trigger_ratio(payload: Mapping[str, object]) -> float:
|
|
131
|
+
from cluxion_runtime.core.context_compress import DEFAULT_TRIGGER_RATIO
|
|
132
|
+
|
|
133
|
+
value = payload.get("trigger_ratio")
|
|
134
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool) and 0.0 < float(value) < 1.0:
|
|
135
|
+
return float(value)
|
|
136
|
+
return DEFAULT_TRIGGER_RATIO
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _context_bool_flag(payload: Mapping[str, object], key: str, default: bool) -> bool:
|
|
140
|
+
value = payload.get(key)
|
|
141
|
+
if isinstance(value, bool):
|
|
142
|
+
return value
|
|
143
|
+
return default
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _finalize_context_compress(body: Mapping[str, object], stage1: dict[str, object]) -> dict[str, object]:
|
|
147
|
+
"""After Rust Stage-1, continue the Python pipeline when still above trigger."""
|
|
123
148
|
from cluxion_runtime.core import context_compress
|
|
124
149
|
|
|
125
|
-
|
|
150
|
+
trigger_ratio = _context_trigger_ratio(body)
|
|
151
|
+
usage_after = float(stage1.get("usage_after", 0))
|
|
152
|
+
if usage_after <= trigger_ratio:
|
|
153
|
+
stage1["reached_target"] = True
|
|
154
|
+
return stage1
|
|
155
|
+
|
|
156
|
+
enable_llm = _context_bool_flag(body, "enable_llm_summary", True)
|
|
157
|
+
enable_forget = _context_bool_flag(body, "enable_forget", True)
|
|
158
|
+
if not enable_llm and not enable_forget:
|
|
159
|
+
stage1["reached_target"] = False
|
|
160
|
+
if stage1.get("ai_summary_request"):
|
|
161
|
+
stage1["requires_summary"] = True
|
|
162
|
+
return stage1
|
|
163
|
+
|
|
164
|
+
continued_body = dict(body)
|
|
165
|
+
messages = stage1.get("messages")
|
|
166
|
+
if isinstance(messages, list):
|
|
167
|
+
continued_body["messages"] = messages
|
|
168
|
+
continued = context_compress.compress(continued_body)
|
|
169
|
+
continued_usage = float(continued.get("usage_after", 1.0))
|
|
170
|
+
continued["reached_target"] = continued_usage <= trigger_ratio
|
|
171
|
+
if not continued["reached_target"] and continued.get("ai_summary_request"):
|
|
172
|
+
continued["requires_summary"] = True
|
|
173
|
+
|
|
174
|
+
stage1_stages = stage1.get("stages_applied")
|
|
175
|
+
continued_stages = continued.get("stages_applied")
|
|
176
|
+
if isinstance(stage1_stages, list) and isinstance(continued_stages, list):
|
|
177
|
+
merged = list(stage1_stages)
|
|
178
|
+
for stage in continued_stages:
|
|
179
|
+
if stage not in merged:
|
|
180
|
+
merged.append(stage)
|
|
181
|
+
continued["stages_applied"] = merged
|
|
182
|
+
return continued
|
|
126
183
|
|
|
127
184
|
|
|
128
185
|
def _invoke(command: str, payload: Mapping[str, object], *, store_dir: Path | None) -> dict[str, object]:
|
|
@@ -121,7 +121,59 @@ def test_model_registry_resolution(backend) -> None:
|
|
|
121
121
|
def test_backend_matches_python_reference(backend) -> None:
|
|
122
122
|
reference = context_compress.compress(dict(_COMPRESSIBLE))
|
|
123
123
|
result = queue_bridge.compress_context(_COMPRESSIBLE)
|
|
124
|
-
|
|
124
|
+
if backend == "python":
|
|
125
|
+
assert result == reference
|
|
126
|
+
else:
|
|
127
|
+
# Stage-1 backends may add reached_target when continuing the pipeline.
|
|
128
|
+
result_compare = {k: v for k, v in result.items() if k not in {"reached_target", "requires_summary"}}
|
|
129
|
+
reference_compare = dict(reference)
|
|
130
|
+
assert result_compare == reference_compare
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_tool_path_continues_when_stage1_above_trigger(monkeypatch) -> None:
|
|
134
|
+
"""Rust Stage-1-only output above trigger must continue into Python pipeline."""
|
|
135
|
+
messages = [
|
|
136
|
+
{"role": "user", "content": "task intent"},
|
|
137
|
+
{"role": "assistant", "content": _long(80_000)},
|
|
138
|
+
{"role": "tool", "content": _long(80_000)},
|
|
139
|
+
{"role": "user", "content": "recent question"},
|
|
140
|
+
]
|
|
141
|
+
full_payload = {
|
|
142
|
+
"messages": messages,
|
|
143
|
+
"context_limit_tokens": 40_000,
|
|
144
|
+
"keep_recent_turns": 1,
|
|
145
|
+
"enable_llm_summary": False,
|
|
146
|
+
"enable_forget": True,
|
|
147
|
+
}
|
|
148
|
+
stage1 = {
|
|
149
|
+
"ok": True,
|
|
150
|
+
"compressed": True,
|
|
151
|
+
"tokens_before": 90_000,
|
|
152
|
+
"tokens_after": 34_000,
|
|
153
|
+
"usage_before": 2.25,
|
|
154
|
+
"usage_after": 0.85,
|
|
155
|
+
"context_limit": 40_000,
|
|
156
|
+
"stages_applied": ["truncate", "digest"],
|
|
157
|
+
"pinned_indices": [0, 3],
|
|
158
|
+
"messages": messages,
|
|
159
|
+
"ai_summary_request": {
|
|
160
|
+
"reason": "deterministic stages insufficient",
|
|
161
|
+
"current_tokens": 34_000,
|
|
162
|
+
"target_tokens": 12_000,
|
|
163
|
+
"summarize_indices": [1, 2],
|
|
164
|
+
"instructions": "summarize",
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
assert float(stage1["usage_after"]) > 0.70
|
|
168
|
+
|
|
169
|
+
monkeypatch.setenv(queue_bridge.QUEUE_BACKEND_ENV, "native")
|
|
170
|
+
monkeypatch.setattr(queue_bridge, "_invoke_native", lambda *a, **k: dict(stage1))
|
|
171
|
+
monkeypatch.setattr(context_compress, "hermes_available", lambda: False)
|
|
172
|
+
|
|
173
|
+
result = queue_bridge.compress_context(full_payload)
|
|
174
|
+
trigger = 0.70
|
|
175
|
+
assert result.get("reached_target") is True
|
|
176
|
+
assert float(result["usage_after"]) <= trigger
|
|
125
177
|
|
|
126
178
|
|
|
127
179
|
def test_missing_messages_raises(backend) -> None:
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import subprocess
|
|
6
7
|
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
7
10
|
from cluxion_runtime.core import context_compress
|
|
8
11
|
from cluxion_runtime.core.context_compress import _Msg
|
|
9
12
|
from cluxion_runtime.core.hybrid_forget import _cold_demote, apply_hybrid_forget
|
|
@@ -270,6 +273,48 @@ def test_hallucination_guard_keeps_korean_normalized_number(monkeypatch) -> None
|
|
|
270
273
|
assert "5433" in result[0]
|
|
271
274
|
|
|
272
275
|
|
|
276
|
+
@pytest.mark.parametrize(
|
|
277
|
+
"fabricated",
|
|
278
|
+
["0.3.7", "0.3.1", "3.17", "0.17", "0.31.7", "1.3.17"],
|
|
279
|
+
)
|
|
280
|
+
def test_hallucination_guard_strips_fabricated_version_parts(fabricated: str, monkeypatch) -> None:
|
|
281
|
+
from cluxion_runtime.core import llm_compress
|
|
282
|
+
|
|
283
|
+
source = "deployed version 0.3.17 of the package"
|
|
284
|
+
llm_json = json.dumps({"0": f"Package version {fabricated} deployed."})
|
|
285
|
+
monkeypatch.setattr(llm_compress, "hermes_available", lambda: True)
|
|
286
|
+
monkeypatch.setattr(llm_compress, "_call_hermes_oneshot", lambda *a, **k: llm_json)
|
|
287
|
+
|
|
288
|
+
result = llm_compress.summarize_messages([_msg(source)], [0])
|
|
289
|
+
assert result is not None
|
|
290
|
+
assert fabricated not in result[0]
|
|
291
|
+
assert "0.3.17" in result[0] or "version" in result[0].lower()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def test_hallucination_guard_keeps_exact_version(monkeypatch) -> None:
|
|
295
|
+
from cluxion_runtime.core import llm_compress
|
|
296
|
+
|
|
297
|
+
source = "deployed version 0.3.17 of the package"
|
|
298
|
+
llm_json = '{"0": "Deployed version 0.3.17."}'
|
|
299
|
+
monkeypatch.setattr(llm_compress, "hermes_available", lambda: True)
|
|
300
|
+
monkeypatch.setattr(llm_compress, "_call_hermes_oneshot", lambda *a, **k: llm_json)
|
|
301
|
+
|
|
302
|
+
result = llm_compress.summarize_messages([_msg(source)], [0])
|
|
303
|
+
assert result is not None
|
|
304
|
+
assert "0.3.17" in result[0]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def test_apply_hallucination_guard_strips_invented_version() -> None:
|
|
308
|
+
from cluxion_runtime.core import llm_compress
|
|
309
|
+
|
|
310
|
+
source = "Current package is version 0.3.17 only."
|
|
311
|
+
summary = "We bumped to version 1.2.3 for this release."
|
|
312
|
+
guarded, stripped = llm_compress._apply_hallucination_guard(summary, source)
|
|
313
|
+
assert stripped >= 1
|
|
314
|
+
assert guarded is not None
|
|
315
|
+
assert "1.2.3" not in guarded
|
|
316
|
+
|
|
317
|
+
|
|
273
318
|
def test_hallucination_guard_all_fabricated_returns_none(monkeypatch) -> None:
|
|
274
319
|
from cluxion_runtime.core import llm_compress
|
|
275
320
|
|
|
@@ -11,13 +11,16 @@ from typing import Any
|
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
13
|
from cluxion_runtime.guard_daemon_host import (
|
|
14
|
+
DEFAULT_IDLE_TTL_MS,
|
|
14
15
|
HEARTBEAT_FILE_NAME,
|
|
15
16
|
PID_FILE_NAME,
|
|
16
17
|
PROC_SCAN_EVERY_N_TICKS,
|
|
18
|
+
STATE_WRITE_EVERY_N_TICKS,
|
|
17
19
|
ProcessScanCache,
|
|
18
20
|
_daemon_loop_step,
|
|
19
21
|
_python_daemon_tick,
|
|
20
22
|
_run_python_daemon,
|
|
23
|
+
_write_state_if_changed,
|
|
21
24
|
is_idle,
|
|
22
25
|
)
|
|
23
26
|
|
|
@@ -178,7 +181,7 @@ def test_daemon_loop_step_exits_idle_and_removes_pidfile(tmp_path: Path) -> None
|
|
|
178
181
|
pid_path.write_text("4242", encoding="utf-8")
|
|
179
182
|
|
|
180
183
|
process_cache = ProcessScanCache(process_count=0, zombie_count=0, zombie_pids=[])
|
|
181
|
-
keep_running, _ = _daemon_loop_step(
|
|
184
|
+
keep_running, _, _ = _daemon_loop_step(
|
|
182
185
|
tmp_path,
|
|
183
186
|
process_cache=process_cache,
|
|
184
187
|
cpu_window=[],
|
|
@@ -218,7 +221,7 @@ def test_daemon_loop_step_keeps_running_with_fresh_heartbeat(tmp_path: Path) ->
|
|
|
218
221
|
pid_path.write_text("4242", encoding="utf-8")
|
|
219
222
|
|
|
220
223
|
process_cache = ProcessScanCache(process_count=0, zombie_count=0, zombie_pids=[])
|
|
221
|
-
keep_running, refreshed_cache = _daemon_loop_step(
|
|
224
|
+
keep_running, refreshed_cache, _ = _daemon_loop_step(
|
|
222
225
|
tmp_path,
|
|
223
226
|
process_cache=process_cache,
|
|
224
227
|
cpu_window=[],
|
|
@@ -233,4 +236,24 @@ def test_daemon_loop_step_keeps_running_with_fresh_heartbeat(tmp_path: Path) ->
|
|
|
233
236
|
assert keep_running is True
|
|
234
237
|
assert pid_path.exists()
|
|
235
238
|
assert refreshed_cache.process_count > 0
|
|
236
|
-
assert (tmp_path / "guard_state.json").exists()
|
|
239
|
+
assert (tmp_path / "guard_state.json").exists()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_default_idle_ttl_is_two_minutes() -> None:
|
|
243
|
+
assert DEFAULT_IDLE_TTL_MS == 120_000
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def test_write_state_if_changed_skips_identical_fingerprint(tmp_path: Path) -> None:
|
|
247
|
+
process_cache = ProcessScanCache(process_count=0, zombie_count=0, zombie_pids=[])
|
|
248
|
+
state, _ = _python_daemon_tick(process_cache, [], [], 5, 1000, tick=0)
|
|
249
|
+
state_path = tmp_path / "guard_state.json"
|
|
250
|
+
|
|
251
|
+
fp = _write_state_if_changed(tmp_path, state, last_fingerprint=None, tick=1)
|
|
252
|
+
first_mtime = state_path.stat().st_mtime
|
|
253
|
+
assert state_path.exists()
|
|
254
|
+
|
|
255
|
+
_write_state_if_changed(tmp_path, state, last_fingerprint=fp, tick=2)
|
|
256
|
+
assert state_path.stat().st_mtime == first_mtime
|
|
257
|
+
|
|
258
|
+
_write_state_if_changed(tmp_path, state, last_fingerprint=fp, tick=STATE_WRITE_EVERY_N_TICKS)
|
|
259
|
+
assert state_path.stat().st_mtime >= first_mtime
|
|
@@ -43,6 +43,7 @@ def test_register_adds_guard_hooks_when_supported() -> None:
|
|
|
43
43
|
|
|
44
44
|
assert ctx.hooks == {
|
|
45
45
|
"on_session_start": guard_watch.on_session_start,
|
|
46
|
+
"on_session_end": guard_watch.on_session_end,
|
|
46
47
|
"post_tool_call": guard_watch.post_tool_call,
|
|
47
48
|
}
|
|
48
49
|
assert "cluxion_guard" in ctx.tools
|
|
@@ -205,6 +205,14 @@ def test_context_compress_rejects_malformed_messages() -> None:
|
|
|
205
205
|
raise AssertionError("expected ValueError")
|
|
206
206
|
|
|
207
207
|
|
|
208
|
+
def test_guard_unknown_action_returns_clear_error() -> None:
|
|
209
|
+
result = runner.guard({"action": "sample"})
|
|
210
|
+
payload = json.loads(result.to_json())
|
|
211
|
+
assert result.ok is False
|
|
212
|
+
assert "unknown guard action: sample" in payload["error"]
|
|
213
|
+
assert "status|start|stop|enforce|auto-enforce" in payload["error"]
|
|
214
|
+
|
|
215
|
+
|
|
208
216
|
def test_context_compress_accepts_valid_messages_structure() -> None:
|
|
209
217
|
try:
|
|
210
218
|
runner.context_compress({"messages": [{"content": "hello world"}]})
|
|
File without changes
|
{cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/.gitignore
RENAMED
|
File without changes
|
{cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/Docs/README.md
RENAMED
|
File without changes
|
{cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/LICENSE
RENAMED
|
File without changes
|
{cluxion_agentplugin_preprocessing-0.3.17 → cluxion_agentplugin_preprocessing-0.3.18}/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
|
|
File without changes
|