cluxion-agentplugin-preprocessing 0.3.6__tar.gz → 0.3.8__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 (90) hide show
  1. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/PKG-INFO +1 -1
  2. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/pyproject.toml +1 -1
  3. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/__init__.py +1 -1
  4. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/doctor/probes.py +150 -1
  5. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/plugin.py +4 -0
  6. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/runner.py +20 -2
  7. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/test_doctor.py +20 -5
  8. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/test_runner.py +35 -0
  9. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/.github/profile/README.md +0 -0
  10. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/.gitignore +0 -0
  11. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/Docs/README.md +0 -0
  12. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/LICENSE +0 -0
  13. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/README.md +0 -0
  14. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/adapters/claude/.claude-plugin/plugin.json +0 -0
  15. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/adapters/claude/skills/preprocess/SKILL.md +0 -0
  16. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/adapters/codex/config-snippet.toml +0 -0
  17. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/cluxion-Docs/README.md +0 -0
  18. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/cluxion-Docs/architecture.md +0 -0
  19. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/cluxion-Docs/harness-logic.md +0 -0
  20. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/cluxion-Docs/honesty-preprocessing.md +0 -0
  21. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/cluxion-Docs/install-and-operations.md +0 -0
  22. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/cluxion-Docs/security.md +0 -0
  23. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/Cargo.lock +0 -0
  24. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/Cargo.toml +0 -0
  25. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/pyproject.toml +0 -0
  26. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/src/context.rs +0 -0
  27. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/src/dispatch.rs +0 -0
  28. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/src/guard.rs +0 -0
  29. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/src/lib.rs +0 -0
  30. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/src/main.rs +0 -0
  31. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/src/queue.rs +0 -0
  32. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/rust/cluxion_queue/src/types.rs +0 -0
  33. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/cli.py +0 -0
  34. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/doctor/__init__.py +0 -0
  35. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/doctor/catalog.json +0 -0
  36. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/doctor/framework.py +0 -0
  37. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/guard_watch.py +0 -0
  38. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/hermes_config.py +0 -0
  39. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/plugin.yaml +0 -0
  40. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_agentplugin_preprocessing/schemas.py +0 -0
  41. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/__init__.py +0 -0
  42. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/__main__.py +0 -0
  43. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/adapters/__init__.py +0 -0
  44. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/adapters/contract.py +0 -0
  45. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/adapters/grok_build.py +0 -0
  46. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/adapters/hermes.py +0 -0
  47. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/adapters/spec.py +0 -0
  48. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/bootstrap.py +0 -0
  49. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/cli.py +0 -0
  50. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/__init__.py +0 -0
  51. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/clarification.py +0 -0
  52. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/context_compress.py +0 -0
  53. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/dispatch_store.py +0 -0
  54. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/harness.py +0 -0
  55. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/intent.py +0 -0
  56. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/ledger.py +0 -0
  57. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/ledger_codec.py +0 -0
  58. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/plan_codec.py +0 -0
  59. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/preprocess.py +0 -0
  60. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/types.py +0 -0
  61. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/core/work_queue.py +0 -0
  62. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/guard_daemon_host.py +0 -0
  63. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/models/__init__.py +0 -0
  64. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/models/supervisor.py +0 -0
  65. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/models/vllm_mlx.py +0 -0
  66. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/resources/__init__.py +0 -0
  67. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/resources/guard_bridge.py +0 -0
  68. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/resources/py_queue.py +0 -0
  69. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/resources/queue_bridge.py +0 -0
  70. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/resources/rust_bridge.py +0 -0
  71. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/web/__init__.py +0 -0
  72. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/src/cluxion_runtime/web/browser_bridge.py +0 -0
  73. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_browser_bridge.py +0 -0
  74. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_clarification.py +0 -0
  75. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_cluxion_runtime_spine.py +0 -0
  76. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_context_compress.py +0 -0
  77. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_contract.py +0 -0
  78. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_dispatch_store.py +0 -0
  79. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_guard.py +0 -0
  80. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_ledger.py +0 -0
  81. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_py_queue_concurrency.py +0 -0
  82. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_queue_backends.py +0 -0
  83. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_runtime_adapter_cli.py +0 -0
  84. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_rust_queue.py +0 -0
  85. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/runtime/test_supervisor.py +0 -0
  86. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/test_bootstrap.py +0 -0
  87. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/test_guard_watch.py +0 -0
  88. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/test_hermes_config.py +0 -0
  89. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/test_packaging_policy.py +0 -0
  90. {cluxion_agentplugin_preprocessing-0.3.6 → cluxion_agentplugin_preprocessing-0.3.8}/tests/test_plugin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cluxion-agentplugin-preprocessing
3
- Version: 0.3.6
3
+ Version: 0.3.8
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.6"
7
+ version = "0.3.8"
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"
@@ -2,6 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.3.6"
5
+ __version__ = "0.3.8"
6
6
 
7
7
  __all__ = ["__version__"]
@@ -3,7 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import importlib.metadata
6
+ import importlib.util
7
+ import json as _json
8
+ import os
6
9
  import shutil
10
+ import sys
11
+ import tempfile
7
12
  from collections.abc import Callable
8
13
 
9
14
  from .framework import DoctorContext
@@ -96,7 +101,7 @@ def native_module_importable(ctx: DoctorContext) -> tuple[str, str]:
96
101
  return "pass", "imported (native backend available)"
97
102
  return "warn", "imported but expected symbols missing"
98
103
  except Exception:
99
- return "warn", "native missing \u2192 using fallback (slower)"
104
+ return "warn", "native missing using fallback (slower)"
100
105
 
101
106
 
102
107
  # plugin-specific probes (deterministic ones only)
@@ -174,4 +179,148 @@ def handler_exception_coverage(ctx: DoctorContext) -> tuple[str, str]:
174
179
  return "skip", f"cannot invoke guard: {e}"
175
180
 
176
181
 
182
+ def _safe_read_hermes_config():
183
+ try:
184
+ import yaml
185
+ cfg_path = os.path.expanduser("~/.hermes/config.yaml")
186
+ if not os.path.exists(cfg_path):
187
+ return None, "absent"
188
+ with open(cfg_path, encoding="utf-8") as f:
189
+ data = yaml.safe_load(f) or {}
190
+ return data, "ok"
191
+ except Exception as e:
192
+ return None, f"read_error:{type(e).__name__}"
193
+
194
+
195
+ @_register("psutil_importable")
196
+ def psutil_importable(ctx: DoctorContext) -> tuple[str, str]:
197
+ try:
198
+ import psutil
199
+ _ = psutil.virtual_memory()
200
+ return "pass", "importable"
201
+ except ImportError:
202
+ return "fail", "psutil not installed"
203
+ except Exception as e:
204
+ return "skip", f"uncertainty: {type(e).__name__}"
205
+
206
+
207
+ @_register("pyyaml_importable")
208
+ def pyyaml_importable(ctx: DoctorContext) -> tuple[str, str]:
209
+ try:
210
+ import yaml
211
+ yaml.safe_load("test: 1")
212
+ return "pass", "importable"
213
+ except ImportError:
214
+ return "fail", "PyYAML not installed"
215
+ except Exception as e:
216
+ return "skip", f"uncertainty: {type(e).__name__}"
217
+
218
+
219
+ @_register("fcntl_available_on_posix")
220
+ def fcntl_available_on_posix(ctx: DoctorContext) -> tuple[str, str]:
221
+ if os.name != "posix":
222
+ return "skip", "non-POSIX (Windows)"
223
+ try:
224
+ import fcntl
225
+ # real check: lock a temp file
226
+ with tempfile.NamedTemporaryFile(delete=False) as tf:
227
+ tf_path = tf.name
228
+ try:
229
+ with open(tf_path, "a+b") as f:
230
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
231
+ fcntl.flock(f.fileno(), fcntl.LOCK_UN)
232
+ return "pass", "fcntl works on POSIX"
233
+ finally:
234
+ os.unlink(tf_path)
235
+ except Exception as e:
236
+ return "skip", f"fcntl issue: {type(e).__name__}"
237
+
238
+
239
+ @_register("playwright_optional_available")
240
+ def playwright_optional_available(ctx: DoctorContext) -> tuple[str, str]:
241
+ if importlib.util.find_spec("playwright") is not None:
242
+ return "pass", "importable"
243
+ return "warn", "optional, not installed"
244
+
245
+
246
+ @_register("abi3_wheel_compatible")
247
+ def abi3_wheel_compatible(ctx: DoctorContext) -> tuple[str, str]:
248
+ # since requires-python >=3.11 the check is always pass
249
+ return "pass", f"Python {sys.version_info.major}.{sys.version_info.minor} >= 3.11 abi3 floor"
250
+
251
+
252
+ @_register("sqlite_wal_mode_compatible")
253
+ def sqlite_wal_mode_compatible(ctx: DoctorContext) -> tuple[str, str]:
254
+ try:
255
+ import sqlite3
256
+ conn = sqlite3.connect(":memory:")
257
+ try:
258
+ mode = conn.execute("PRAGMA journal_mode=WAL").fetchone()[0]
259
+ ver = sqlite3.sqlite_version
260
+ if str(mode).lower() == "wal":
261
+ return "pass", f"wal supported (sqlite {ver})"
262
+ return "warn", f"got {mode} (sqlite {ver})"
263
+ finally:
264
+ conn.close()
265
+ except Exception as e:
266
+ return "skip", f"uncertainty: {type(e).__name__}"
267
+
268
+
269
+ @_register("json_serialization_deterministic")
270
+ def json_serialization_deterministic(ctx: DoctorContext) -> tuple[str, str]:
271
+ try:
272
+ d = {"z": 1, "a": 2, "nested": {"b": 3}}
273
+ j1 = _json.dumps(d, sort_keys=True, separators=(",", ":"))
274
+ j2 = _json.dumps(d, sort_keys=True, separators=(",", ":"))
275
+ if j1 == j2:
276
+ return "pass", "roundtrip bytes equal"
277
+ return "fail", "non-deterministic"
278
+ except Exception as e:
279
+ return "skip", f"uncertainty: {type(e).__name__}"
280
+
281
+
282
+ @_register("hermes_plugin_enabled")
283
+ def hermes_plugin_enabled(ctx: DoctorContext) -> tuple[str, str]:
284
+ data, status = _safe_read_hermes_config()
285
+ if status != "ok":
286
+ return "skip", f"config not present: {status}"
287
+ try:
288
+ plugins = (data or {}).get("plugins", {}) or {}
289
+ enabled = plugins.get("enabled", []) or []
290
+ disabled = plugins.get("disabled", []) or []
291
+ names = ["cluxion-agentplugin-preprocessing", "hermes-cluxion"]
292
+ for n in names:
293
+ if n in enabled and n not in disabled:
294
+ return "pass", f"{n} in enabled"
295
+ if any(n in enabled for n in names):
296
+ return "warn", "present but also disabled?"
297
+ return "warn", "not in plugins.enabled"
298
+ except Exception as e:
299
+ return "skip", f"uncertainty: {type(e).__name__}"
300
+
301
+
302
+ @_register("env_var_consistency")
303
+ def env_var_consistency(ctx: DoctorContext) -> tuple[str, str]:
304
+ try:
305
+ known = [
306
+ "CLUXION_QUEUE_STORE_DIR",
307
+ "CLUXION_PREPROCESS_DISPATCH_DIR",
308
+ "CLUXION_QUEUE_BACKEND",
309
+ ]
310
+ issues = []
311
+ for var in known:
312
+ val = os.environ.get(var)
313
+ if val and "DIR" in var:
314
+ try:
315
+ p = os.path.expanduser(val)
316
+ os.makedirs(p, exist_ok=True)
317
+ except Exception:
318
+ issues.append(f"{var}=invalid_path")
319
+ if issues:
320
+ return "warn", ";".join(issues)
321
+ return "pass", "defaults or valid"
322
+ except Exception as e:
323
+ return "skip", f"uncertainty: {type(e).__name__}"
324
+
325
+
177
326
  # note: other checks in catalog will be reported as skip (no probe)
@@ -247,6 +247,10 @@ def _handle_browser_type(args: dict[str, object], **_: object) -> str:
247
247
 
248
248
 
249
249
  def _handle_doctor(args: dict[str, object], **_: object) -> str:
250
+ return _json_result(lambda: _run_doctor(args))
251
+
252
+
253
+ def _run_doctor(args: dict[str, object]) -> str:
250
254
  pkg = "cluxion_agentplugin_preprocessing.doctor"
251
255
  catalog_path = Path(str(importlib.resources.files(pkg).joinpath("catalog.json")))
252
256
  result = run_doctor(
@@ -65,6 +65,7 @@ def bootstrap(payload: Mapping[str, object], *, command_runner: CommandRunner |
65
65
  def serve_local(payload: Mapping[str, object], *, command_runner: CommandRunner | None = None) -> RuntimeResult:
66
66
  """Run cluxion-runtime serve-local with dry-run by default."""
67
67
  model = _required_str(payload, "model")
68
+ port = _port(payload, "port", 23003)
68
69
  command = [
69
70
  _runtime_binary(None),
70
71
  "serve-local",
@@ -73,7 +74,7 @@ def serve_local(payload: Mapping[str, object], *, command_runner: CommandRunner
73
74
  "--host",
74
75
  _str(payload, "host", "127.0.0.1"),
75
76
  "--port",
76
- str(_int(payload, "port", 23003)),
77
+ str(port),
77
78
  "--max-tokens",
78
79
  str(_int(payload, "max_tokens", 128_000)),
79
80
  ]
@@ -89,6 +90,7 @@ def serve_local(payload: Mapping[str, object], *, command_runner: CommandRunner
89
90
  def hermes_config(payload: Mapping[str, object], *, command_runner: CommandRunner | None = None) -> RuntimeResult:
90
91
  """Render Hermes config patch for a local endpoint."""
91
92
  model = _required_str(payload, "model")
93
+ port = _port(payload, "port", 23003)
92
94
  command = (
93
95
  _runtime_binary(None),
94
96
  "hermes-local-config",
@@ -97,7 +99,7 @@ def hermes_config(payload: Mapping[str, object], *, command_runner: CommandRunne
97
99
  "--host",
98
100
  _str(payload, "host", "127.0.0.1"),
99
101
  "--port",
100
- str(_int(payload, "port", 23003)),
102
+ str(port),
101
103
  "--context-length",
102
104
  str(_int(payload, "context_length", 131_072)),
103
105
  "--provider-key",
@@ -144,6 +146,12 @@ def queue_brief(payload: Mapping[str, object], *, command_runner: CommandRunner
144
146
 
145
147
  def context_compress(payload: Mapping[str, object], *, command_runner: CommandRunner | None = None) -> RuntimeResult:
146
148
  """Compress conversation context deterministically once it exceeds the trigger ratio."""
149
+ messages = payload.get("messages")
150
+ if not isinstance(messages, list):
151
+ raise ValueError("messages must be a list of objects")
152
+ for i, m in enumerate(messages):
153
+ if not isinstance(m, dict) or not isinstance(m.get("content"), str):
154
+ raise ValueError(f"message[{i}] must be an object with string content")
147
155
  command = (_runtime_binary(None), "context-compress", "--json-stdin")
148
156
  stdin = json.dumps(dict(payload), ensure_ascii=False)
149
157
  return _execute_json(command, stdin, command_runner)
@@ -318,6 +326,16 @@ def _int(payload: Mapping[str, object], key: str, default: int) -> int:
318
326
  return value if value > 0 else default
319
327
 
320
328
 
329
+ def _port(payload: Mapping[str, object], key: str, default: int) -> int:
330
+ try:
331
+ value = int(payload.get(key, default))
332
+ except (TypeError, ValueError):
333
+ value = default
334
+ if not (1 <= value <= 65535):
335
+ raise ValueError(f"{key} must be between 1 and 65535")
336
+ return value
337
+
338
+
321
339
  def _non_negative_int(payload: Mapping[str, object], key: str, default: int) -> int:
322
340
  try:
323
341
  value = int(payload.get(key, default))
@@ -24,7 +24,7 @@ def test_run_doctor_returns_result_and_deterministic():
24
24
  catalog_path=cat,
25
25
  probes=PROBES,
26
26
  plugin="preprocessing",
27
- version="0.3.6",
27
+ version="0.3.7",
28
28
  )
29
29
  assert isinstance(r1, DoctorResult)
30
30
  j1 = render_json(r1)
@@ -33,7 +33,7 @@ def test_run_doctor_returns_result_and_deterministic():
33
33
  catalog_path=cat,
34
34
  probes=PROBES,
35
35
  plugin="preprocessing",
36
- version="0.3.6",
36
+ version="0.3.7",
37
37
  )
38
38
  j2 = render_json(r2)
39
39
  assert j1 == j2 # byte identical
@@ -49,7 +49,7 @@ def test_cross_cutting_checks_present():
49
49
  catalog_path=cat,
50
50
  probes=PROBES,
51
51
  plugin="preprocessing",
52
- version="0.3.6",
52
+ version="0.3.7",
53
53
  )
54
54
  statuses = {c.check_id: c.status for c in result.checks}
55
55
  for key in ("hermes_on_path", "entry_point_registered", "toolset_valid"):
@@ -57,6 +57,21 @@ def test_cross_cutting_checks_present():
57
57
  assert statuses[key] in ("pass", "warn", "fail", "skip")
58
58
 
59
59
 
60
+ def test_new_probes_non_skip():
61
+ cat = _catalog_path()
62
+ result = run_doctor(
63
+ cwd=Path.cwd(),
64
+ catalog_path=cat,
65
+ probes=PROBES,
66
+ plugin="preprocessing",
67
+ version="0.3.7",
68
+ )
69
+ statuses = {c.check_id: c.status for c in result.checks}
70
+ for key in ("psutil_importable", "json_serialization_deterministic"):
71
+ assert key in statuses
72
+ assert statuses[key] in ("pass", "warn", "fail") # non-skip
73
+
74
+
60
75
  def test_probe_exception_becomes_fail():
61
76
  def bad_probe(ctx):
62
77
  raise RuntimeError("boom")
@@ -66,7 +81,7 @@ def test_probe_exception_becomes_fail():
66
81
  catalog_path=_catalog_path(),
67
82
  probes={"hermes_on_path": bad_probe},
68
83
  plugin="preprocessing",
69
- version="0.3.6",
84
+ version="0.3.7",
70
85
  )
71
86
  statuses = {c.check_id: c.status for c in result.checks}
72
87
  assert statuses["hermes_on_path"] == "fail"
@@ -78,5 +93,5 @@ def test_warn_only_is_ok():
78
93
  checks = (
79
94
  CheckResult(check_id="x", category="c", severity="medium", status="warn", detail="w"),
80
95
  )
81
- r = DoctorResult(plugin="p", version="0.3.6", checks=checks)
96
+ r = DoctorResult(plugin="p", version="0.3.7", checks=checks)
82
97
  assert r.ok is True
@@ -176,3 +176,38 @@ def _isolated_env(key: str, value: str) -> Iterator[None]:
176
176
  os.environ.pop(key, None)
177
177
  else:
178
178
  os.environ[key] = old_value
179
+
180
+
181
+ def test_serve_local_rejects_out_of_range_port() -> None:
182
+ try:
183
+ runner.serve_local({"model": "test", "port": 999999999})
184
+ except ValueError as exc:
185
+ assert "port must be between 1 and 65535" in str(exc)
186
+ else:
187
+ raise AssertionError("expected ValueError")
188
+
189
+
190
+ def test_hermes_config_rejects_invalid_port() -> None:
191
+ try:
192
+ runner.hermes_config({"model": "test", "port": 0})
193
+ except ValueError as exc:
194
+ assert "port must be between 1 and 65535" in str(exc)
195
+ else:
196
+ raise AssertionError("expected ValueError")
197
+
198
+
199
+ def test_context_compress_rejects_malformed_messages() -> None:
200
+ try:
201
+ runner.context_compress({"messages": [42, "x", None, {"role": "user"}]})
202
+ except ValueError as exc:
203
+ assert "message[0] must be an object with string content" in str(exc)
204
+ else:
205
+ raise AssertionError("expected ValueError")
206
+
207
+
208
+ def test_context_compress_accepts_valid_messages_structure() -> None:
209
+ try:
210
+ runner.context_compress({"messages": [{"content": "hello world"}]})
211
+ except ValueError as exc:
212
+ assert "must be an object with string content" not in str(exc)
213
+ # ok if other error like runtime missing