cluxion-agentplugin-preprocessing 0.3.19__tar.gz → 0.3.21__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.
Files changed (97) hide show
  1. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/PKG-INFO +1 -1
  2. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/pyproject.toml +1 -1
  3. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/src/context.rs +6 -4
  4. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/plugin.yaml +1 -1
  5. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/context_compress.py +3 -1
  6. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/hybrid_forget.py +13 -2
  7. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/llm_compress.py +14 -2
  8. cluxion_agentplugin_preprocessing-0.3.21/tests/runtime/test_bin_resolution.py +103 -0
  9. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_context_compress.py +6 -1
  10. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_context_compress_llm_forget.py +12 -2
  11. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/.github/profile/README.md +0 -0
  12. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/.gitignore +0 -0
  13. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/Docs/README.md +0 -0
  14. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/LICENSE +0 -0
  15. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/README.md +0 -0
  16. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/adapters/claude/.claude-plugin/plugin.json +0 -0
  17. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/adapters/claude/skills/preprocess/SKILL.md +0 -0
  18. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/adapters/codex/config-snippet.toml +0 -0
  19. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/cluxion-Docs/README.md +0 -0
  20. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/cluxion-Docs/architecture.md +0 -0
  21. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/cluxion-Docs/harness-logic.md +0 -0
  22. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/cluxion-Docs/honesty-preprocessing.md +0 -0
  23. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/cluxion-Docs/install-and-operations.md +0 -0
  24. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/cluxion-Docs/security.md +0 -0
  25. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/Cargo.lock +0 -0
  26. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/Cargo.toml +0 -0
  27. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/pyproject.toml +0 -0
  28. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/src/dispatch.rs +0 -0
  29. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/src/guard.rs +0 -0
  30. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/src/lib.rs +0 -0
  31. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/src/main.rs +0 -0
  32. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/src/queue.rs +0 -0
  33. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/rust/cluxion_queue/src/types.rs +0 -0
  34. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/__init__.py +0 -0
  35. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/cli.py +0 -0
  36. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/doctor/__init__.py +0 -0
  37. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/doctor/catalog.json +0 -0
  38. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/doctor/framework.py +0 -0
  39. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/doctor/probes.py +0 -0
  40. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/guard_watch.py +0 -0
  41. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/hermes_config.py +0 -0
  42. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/plugin.py +0 -0
  43. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/runner.py +0 -0
  44. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_agentplugin_preprocessing/schemas.py +0 -0
  45. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/__init__.py +0 -0
  46. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/__main__.py +0 -0
  47. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/adapters/__init__.py +0 -0
  48. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/adapters/contract.py +0 -0
  49. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/adapters/grok_build.py +0 -0
  50. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/adapters/hermes.py +0 -0
  51. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/adapters/spec.py +0 -0
  52. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/bootstrap.py +0 -0
  53. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/cli.py +0 -0
  54. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/__init__.py +0 -0
  55. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/clarification.py +0 -0
  56. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/dispatch_store.py +0 -0
  57. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/harness.py +0 -0
  58. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/intent.py +0 -0
  59. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/ledger.py +0 -0
  60. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/ledger_codec.py +0 -0
  61. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/plan_codec.py +0 -0
  62. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/preprocess.py +0 -0
  63. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/types.py +0 -0
  64. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/core/work_queue.py +0 -0
  65. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/guard_daemon_host.py +0 -0
  66. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/models/__init__.py +0 -0
  67. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/models/supervisor.py +0 -0
  68. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/models/vllm_mlx.py +0 -0
  69. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/resources/__init__.py +0 -0
  70. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/resources/guard_bridge.py +0 -0
  71. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/resources/py_queue.py +0 -0
  72. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/resources/queue_bridge.py +0 -0
  73. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/resources/rust_bridge.py +0 -0
  74. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/web/__init__.py +0 -0
  75. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/src/cluxion_runtime/web/browser_bridge.py +0 -0
  76. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_auto_compress_middleware.py +0 -0
  77. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_browser_bridge.py +0 -0
  78. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_clarification.py +0 -0
  79. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_cluxion_runtime_spine.py +0 -0
  80. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_contract.py +0 -0
  81. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_dispatch_store.py +0 -0
  82. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_estimate_tokens.py +0 -0
  83. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_guard.py +0 -0
  84. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_guard_daemon_host.py +0 -0
  85. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_ledger.py +0 -0
  86. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_py_queue_concurrency.py +0 -0
  87. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_queue_backends.py +0 -0
  88. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_runtime_adapter_cli.py +0 -0
  89. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_rust_queue.py +0 -0
  90. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/runtime/test_supervisor.py +0 -0
  91. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/test_bootstrap.py +0 -0
  92. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/test_doctor.py +0 -0
  93. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/test_guard_watch.py +0 -0
  94. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/test_hermes_config.py +0 -0
  95. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/test_packaging_policy.py +0 -0
  96. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/test_plugin.py +0 -0
  97. {cluxion_agentplugin_preprocessing-0.3.19 → cluxion_agentplugin_preprocessing-0.3.21}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cluxion-agentplugin-preprocessing
3
- Version: 0.3.19
3
+ Version: 0.3.21
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cluxion-agentplugin-preprocessing"
7
- version = "0.3.19"
7
+ version = "0.3.21"
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"
@@ -46,10 +46,12 @@ struct Msg {
46
46
  }
47
47
 
48
48
  pub fn compress(payload: &Value) -> Result<Value, QueueError> {
49
- let raw_messages = payload
50
- .get("messages")
51
- .and_then(Value::as_array)
52
- .ok_or_else(|| QueueError::Usage("missing required field: messages".into()))?;
49
+ let raw_messages = match payload.get("messages") {
50
+ None => return Err(QueueError::Usage("missing required field: messages".into())),
51
+ Some(value) => value.as_array().ok_or_else(|| {
52
+ QueueError::Usage("messages must be a list".into())
53
+ })?,
54
+ };
53
55
  let mut messages: Vec<Msg> = Vec::with_capacity(raw_messages.len());
54
56
  for raw in raw_messages {
55
57
  messages.push(Msg {
@@ -1,5 +1,5 @@
1
1
  name: cluxion-agentplugin-preprocessing
2
- version: 0.3.19
2
+ version: 0.3.20
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
@@ -58,8 +58,10 @@ class _Msg:
58
58
 
59
59
  def compress(payload: Mapping[str, object]) -> dict[str, object]:
60
60
  raw_messages = payload.get("messages")
61
- if not isinstance(raw_messages, list):
61
+ if raw_messages is None:
62
62
  raise RuntimeError("missing required field: messages")
63
+ if not isinstance(raw_messages, list):
64
+ raise RuntimeError("messages must be a list")
63
65
  messages = [
64
66
  _Msg(
65
67
  role=str(raw.get("role", "user")) if isinstance(raw, dict) else "user",
@@ -6,9 +6,11 @@ is removed permanently. Stage 3 is Python-only; the Rust mirror does not run thi
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import os
9
10
  import re
10
11
  import shutil
11
12
  import subprocess
13
+ import sys
12
14
  import uuid
13
15
  from dataclasses import dataclass
14
16
  from typing import TYPE_CHECKING, Protocol
@@ -18,7 +20,14 @@ from cluxion_runtime.core.preprocess import estimate_tokens
18
20
  if TYPE_CHECKING:
19
21
  from collections.abc import Sequence
20
22
 
21
- _FORGETFORGE_BIN = "forgetforge"
23
+ def _resolve_bin(name: str) -> str:
24
+ candidate = os.path.join(os.path.dirname(sys.executable), name)
25
+ if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
26
+ return candidate
27
+ return shutil.which(name) or name
28
+
29
+
30
+ _FORGETFORGE_BIN = _resolve_bin("forgetforge")
22
31
  _JUNK_MARKERS = ("[cluxion: duplicate", "[cluxion digest]")
23
32
  _IMPORTANCE_KEYWORDS = (
24
33
  "decide",
@@ -75,7 +84,9 @@ class ForgetResult:
75
84
 
76
85
 
77
86
  def forgetforge_available() -> bool:
78
- return shutil.which(_FORGETFORGE_BIN) is not None
87
+ return (os.path.isfile(_FORGETFORGE_BIN) and os.access(_FORGETFORGE_BIN, os.X_OK)) or (
88
+ shutil.which(_FORGETFORGE_BIN) is not None
89
+ )
79
90
 
80
91
 
81
92
  def apply_hybrid_forget(
@@ -13,13 +13,23 @@ import os
13
13
  import re
14
14
  import shutil
15
15
  import subprocess
16
+ import sys
16
17
  from typing import TYPE_CHECKING, Protocol
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from collections.abc import Mapping, Sequence
20
21
 
21
22
  DEFAULT_TIMEOUT_S = 120.0
22
- _HERMES_BIN = "hermes"
23
+
24
+
25
+ def _resolve_bin(name: str) -> str:
26
+ candidate = os.path.join(os.path.dirname(sys.executable), name)
27
+ if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
28
+ return candidate
29
+ return shutil.which(name) or name
30
+
31
+
32
+ _HERMES_BIN = _resolve_bin("hermes")
23
33
  logger = logging.getLogger(__name__)
24
34
 
25
35
  _SUMMARY_INSTRUCTIONS = (
@@ -54,7 +64,9 @@ class _MessageLike(Protocol):
54
64
 
55
65
 
56
66
  def hermes_available() -> bool:
57
- return shutil.which(_HERMES_BIN) is not None
67
+ return (os.path.isfile(_HERMES_BIN) and os.access(_HERMES_BIN, os.X_OK)) or (
68
+ shutil.which(_HERMES_BIN) is not None
69
+ )
58
70
 
59
71
 
60
72
  def summarize_messages(
@@ -0,0 +1,103 @@
1
+ """PATH-independent resolution for forgetforge and hermes console scripts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import stat
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from cluxion_runtime.core import hybrid_forget, llm_compress
13
+
14
+
15
+ def _write_executable(path: Path) -> None:
16
+ path.write_text("#!/bin/sh\necho ok\n")
17
+ path.chmod(path.stat().st_mode | stat.S_IXUSR)
18
+
19
+
20
+ @pytest.fixture
21
+ def isolated_bin_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
22
+ """Temp venv bin dir; PATH excludes it so only sys.executable dir can resolve bins."""
23
+ bin_dir = tmp_path / "bin"
24
+ bin_dir.mkdir()
25
+ monkeypatch.setenv("PATH", str(tmp_path / "empty"))
26
+ monkeypatch.setattr(sys, "executable", str(bin_dir / "python"))
27
+ return bin_dir
28
+
29
+
30
+ def test_forgetforge_available_via_venv_bin_without_path(
31
+ isolated_bin_dir: Path,
32
+ monkeypatch: pytest.MonkeyPatch,
33
+ ) -> None:
34
+ forgetforge = isolated_bin_dir / "forgetforge"
35
+ _write_executable(forgetforge)
36
+ monkeypatch.setattr(hybrid_forget, "_FORGETFORGE_BIN", hybrid_forget._resolve_bin("forgetforge"))
37
+
38
+ assert hybrid_forget.forgetforge_available() is True
39
+ assert os.path.isabs(hybrid_forget._FORGETFORGE_BIN)
40
+ assert str(forgetforge) == hybrid_forget._FORGETFORGE_BIN
41
+
42
+
43
+ def test_forgetforge_unavailable_when_missing_everywhere(
44
+ isolated_bin_dir: Path,
45
+ monkeypatch: pytest.MonkeyPatch,
46
+ ) -> None:
47
+ monkeypatch.setattr(hybrid_forget, "_FORGETFORGE_BIN", hybrid_forget._resolve_bin("forgetforge"))
48
+ assert hybrid_forget.forgetforge_available() is False
49
+
50
+
51
+ def test_forgetforge_available_via_path_fallback(
52
+ tmp_path: Path,
53
+ monkeypatch: pytest.MonkeyPatch,
54
+ ) -> None:
55
+ bin_dir = tmp_path / "on-path"
56
+ bin_dir.mkdir()
57
+ forgetforge = bin_dir / "forgetforge"
58
+ _write_executable(forgetforge)
59
+
60
+ monkeypatch.setenv("PATH", str(bin_dir))
61
+ monkeypatch.setattr(sys, "executable", str(tmp_path / "venv" / "bin" / "python"))
62
+ monkeypatch.setattr(hybrid_forget, "_FORGETFORGE_BIN", hybrid_forget._resolve_bin("forgetforge"))
63
+
64
+ assert hybrid_forget.forgetforge_available() is True
65
+ assert str(forgetforge) == hybrid_forget._FORGETFORGE_BIN
66
+
67
+
68
+ def test_hermes_available_via_venv_bin_without_path(
69
+ isolated_bin_dir: Path,
70
+ monkeypatch: pytest.MonkeyPatch,
71
+ ) -> None:
72
+ hermes = isolated_bin_dir / "hermes"
73
+ _write_executable(hermes)
74
+ monkeypatch.setattr(llm_compress, "_HERMES_BIN", llm_compress._resolve_bin("hermes"))
75
+
76
+ assert llm_compress.hermes_available() is True
77
+ assert os.path.isabs(llm_compress._HERMES_BIN)
78
+ assert str(hermes) == llm_compress._HERMES_BIN
79
+
80
+
81
+ def test_hermes_unavailable_when_missing_everywhere(
82
+ isolated_bin_dir: Path,
83
+ monkeypatch: pytest.MonkeyPatch,
84
+ ) -> None:
85
+ monkeypatch.setattr(llm_compress, "_HERMES_BIN", llm_compress._resolve_bin("hermes"))
86
+ assert llm_compress.hermes_available() is False
87
+
88
+
89
+ def test_hermes_available_via_path_fallback(
90
+ tmp_path: Path,
91
+ monkeypatch: pytest.MonkeyPatch,
92
+ ) -> None:
93
+ bin_dir = tmp_path / "on-path"
94
+ bin_dir.mkdir()
95
+ hermes = bin_dir / "hermes"
96
+ _write_executable(hermes)
97
+
98
+ monkeypatch.setenv("PATH", str(bin_dir))
99
+ monkeypatch.setattr(sys, "executable", str(tmp_path / "venv" / "bin" / "python"))
100
+ monkeypatch.setattr(llm_compress, "_HERMES_BIN", llm_compress._resolve_bin("hermes"))
101
+
102
+ assert llm_compress.hermes_available() is True
103
+ assert str(hermes) == llm_compress._HERMES_BIN
@@ -177,5 +177,10 @@ def test_tool_path_continues_when_stage1_above_trigger(monkeypatch) -> None:
177
177
 
178
178
 
179
179
  def test_missing_messages_raises(backend) -> None:
180
- with pytest.raises(RuntimeError, match="messages"):
180
+ with pytest.raises(RuntimeError, match="missing required field: messages"):
181
181
  queue_bridge.compress_context({"context_limit_tokens": 1000})
182
+
183
+
184
+ def test_non_list_messages_raises(backend) -> None:
185
+ with pytest.raises(RuntimeError, match="messages must be a list"):
186
+ queue_bridge.compress_context({"messages": "not-a-list", "context_limit_tokens": 1000})
@@ -9,7 +9,11 @@ import pytest
9
9
 
10
10
  from cluxion_runtime.core import context_compress
11
11
  from cluxion_runtime.core.context_compress import _Msg
12
- from cluxion_runtime.core.hybrid_forget import _cold_demote, apply_hybrid_forget
12
+ from cluxion_runtime.core.hybrid_forget import (
13
+ _FORGETFORGE_BIN,
14
+ _cold_demote,
15
+ apply_hybrid_forget,
16
+ )
13
17
  from cluxion_runtime.core.preprocess import estimate_tokens
14
18
 
15
19
 
@@ -95,7 +99,13 @@ def test_cold_demote_returns_true_on_successful_store_only(monkeypatch) -> None:
95
99
 
96
100
  assert _cold_demote("recoverable content", "sess-abc123") is True
97
101
  assert len(calls) == 1
98
- assert calls[0] == ["forgetforge", "store", "sess-abc123", "--content", "recoverable content"]
102
+ assert calls[0] == [
103
+ _FORGETFORGE_BIN,
104
+ "store",
105
+ "sess-abc123",
106
+ "--content",
107
+ "recoverable content",
108
+ ]
99
109
 
100
110
 
101
111
  def test_cold_demote_returns_false_when_store_fails(monkeypatch) -> None: