drydock-cli 3.0.75__tar.gz → 3.0.76__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 (98) hide show
  1. {drydock_cli-3.0.75/drydock_cli.egg-info → drydock_cli-3.0.76}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/providers.py +30 -0
  3. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/app.py +28 -4
  4. {drydock_cli-3.0.75 → drydock_cli-3.0.76/drydock_cli.egg-info}/PKG-INFO +1 -1
  5. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/SOURCES.txt +1 -0
  6. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/pyproject.toml +1 -1
  7. drydock_cli-3.0.76/tests/test_server_probe.py +39 -0
  8. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/LICENSE +0 -0
  9. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/NOTICE +0 -0
  10. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/README.md +0 -0
  11. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/__init__.py +0 -0
  12. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/__main__.py +0 -0
  13. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/agent.py +0 -0
  14. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/bash_safety.py +0 -0
  15. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/__init__.py +0 -0
  16. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-categorize.md +0 -0
  17. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-control.md +0 -0
  18. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-poam.md +0 -0
  19. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-review.md +0 -0
  20. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/stig-assess.md +0 -0
  21. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/stig-remediate.md +0 -0
  22. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/cci.py +0 -0
  23. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/cli.py +0 -0
  24. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/compaction.py +0 -0
  25. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/config.py +0 -0
  26. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/detect.py +0 -0
  27. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/extract.py +0 -0
  28. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/gittools.py +0 -0
  29. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/graphrag.py +0 -0
  30. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/guards.py +0 -0
  31. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/loop_detect.py +0 -0
  32. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/mcp.py +0 -0
  33. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/rmf.py +0 -0
  34. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/rmf_graph.py +0 -0
  35. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/skills.py +0 -0
  36. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/stig.py +0 -0
  37. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tool_registry.py +0 -0
  38. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tools/__init__.py +0 -0
  39. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/__init__.py +0 -0
  40. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/approval.py +0 -0
  41. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/messages.py +0 -0
  42. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/widgets.py +0 -0
  43. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tuning.py +0 -0
  44. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/web.py +0 -0
  45. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/dependency_links.txt +0 -0
  46. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/entry_points.txt +0 -0
  47. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/requires.txt +0 -0
  48. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/top_level.txt +0 -0
  49. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/setup.cfg +0 -0
  50. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_approval.py +0 -0
  51. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_back_command.py +0 -0
  52. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_output_bounding.py +0 -0
  53. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_process_group.py +0 -0
  54. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_safety.py +0 -0
  55. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_timeout_network.py +0 -0
  56. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_cci.py +0 -0
  57. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_cli_agents.py +0 -0
  58. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_compact_command.py +0 -0
  59. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_compaction.py +0 -0
  60. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_config.py +0 -0
  61. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_config_migration.py +0 -0
  62. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_context_limit_config.py +0 -0
  63. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_detect.py +0 -0
  64. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_dispatch.py +0 -0
  65. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_e2e_connected.py +0 -0
  66. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_empty_response.py +0 -0
  67. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_extract.py +0 -0
  68. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_failure_loop.py +0 -0
  69. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_first_run_setup.py +0 -0
  70. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_gittools.py +0 -0
  71. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_graphrag.py +0 -0
  72. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_guards_and_tools.py +0 -0
  73. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_hallucinated_tools.py +0 -0
  74. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_leaked_tool_call.py +0 -0
  75. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_loop_detect.py +0 -0
  76. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_mcp.py +0 -0
  77. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_oneshot_unreachable.py +0 -0
  78. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_plan_autocontinue.py +0 -0
  79. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_providers_unreachable.py +0 -0
  80. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_read_index.py +0 -0
  81. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_rmf.py +0 -0
  82. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_rmf_graph.py +0 -0
  83. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_rmf_stig_graph.py +0 -0
  84. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_runaway_repetition.py +0 -0
  85. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_skills.py +0 -0
  86. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_stig.py +0 -0
  87. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_stop.py +0 -0
  88. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_streaming_newlines.py +0 -0
  89. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_subagent.py +0 -0
  90. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_system_prompt_help.py +0 -0
  91. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_todo.py +0 -0
  92. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tool_arg_parsing.py +0 -0
  93. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tools_undo.py +0 -0
  94. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tui.py +0 -0
  95. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tuning.py +0 -0
  96. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_vision_input.py +0 -0
  97. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_web_tools.py +0 -0
  98. {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_xccdf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.75
3
+ Version: 3.0.76
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -56,6 +56,36 @@ PROVIDERS: dict[str, dict] = {
56
56
  }
57
57
 
58
58
 
59
+ def probe_server_context(base_url: str, *, timeout: float = 4.0) -> int | None:
60
+ """Best-effort: ask the model server its REAL context window (tokens).
61
+ llama.cpp exposes it on `/props` (n_ctx); vLLM puts `max_model_len` on
62
+ `/v1/models`. Returns None if unknown/unreachable — never raises. This is the
63
+ definitive diagnostic for a "stuck at N tokens" cap that isn't drydock's
64
+ config (it's the server's own `-c` / `--ctx-size` / `max_model_len`)."""
65
+ import json
66
+ import urllib.request
67
+
68
+ root = base_url.rstrip("/")
69
+ base_no_v1 = root[:-3].rstrip("/") if root.endswith("/v1") else root
70
+
71
+ def _n_ctx(d):
72
+ return (d.get("default_generation_settings") or {}).get("n_ctx") or d.get("n_ctx")
73
+
74
+ def _max_model_len(d):
75
+ return next((m.get("max_model_len") for m in d.get("data", []) if m.get("max_model_len")), None)
76
+
77
+ for url, pick in ((base_no_v1 + "/props", _n_ctx), (root + "/models", _max_model_len)):
78
+ try:
79
+ with urllib.request.urlopen(url, timeout=timeout) as r: # noqa: S310 (user's own server)
80
+ data = json.loads(r.read().decode("utf-8", "ignore"))
81
+ val = pick(data)
82
+ if isinstance(val, int) and val > 0:
83
+ return val
84
+ except Exception: # noqa: BLE001 — best-effort probe
85
+ continue
86
+ return None
87
+
88
+
59
89
  class LLMUnreachable(RuntimeError):
60
90
  """The configured LLM endpoint could not be reached. Carries a
61
91
  user-facing message with remediation steps (shown verbatim in the TUI)."""
@@ -758,12 +758,12 @@ class DrydockApp(App):
758
758
  arg = (arg or "").strip()
759
759
  if not arg:
760
760
  self._info(
761
- f"context_limit: {limit:,} tokens (the '/{limit // 1024}k' in the ctx gauge).\n"
761
+ f"context_limit (drydock): {limit:,} tokens (the '/{limit // 1024}k' in the ctx gauge).\n"
762
762
  "Source order: built-in default 65536 < ~/.drydock/config.toml < --context-limit.\n"
763
- "If you're stuck below this, your MODEL SERVER's context is smaller — raise its\n"
764
- "`-c`/`--ctx-size`/`max_model_len` to match. To change drydock's budget:\n"
765
- " /context <tokens> e.g. /context 65536 (saved to config.toml)"
763
+ "Probing your model server for its REAL context window…\n"
764
+ " To change drydock's budget: /context <tokens> (saved to config.toml)"
766
765
  )
766
+ self.run_worker(lambda: self._probe_server_context(limit), thread=True)
767
767
  return
768
768
  try:
769
769
  n = int(arg.replace(",", "").replace("k", "000").replace("K", "000"))
@@ -781,6 +781,30 @@ class DrydockApp(App):
781
781
  "least this, or the server will still cap you below it."
782
782
  )
783
783
 
784
+ def _probe_server_context(self, limit: int) -> None:
785
+ """Worker: ask the model server its real context window and report whether
786
+ IT (not drydock's config) is the thing capping you — the definitive answer
787
+ to 'stuck at N tokens'."""
788
+ from drydock import providers
789
+
790
+ base_url = self.config.get("base_url") or providers.PROVIDERS.get(
791
+ self.config.get("provider") or "vllm", {}).get("base_url", "http://localhost:8000/v1")
792
+ n_ctx = providers.probe_server_context(base_url)
793
+ if n_ctx is None:
794
+ msg = (f"Model server ({base_url}) didn't report its context size "
795
+ "(not llama.cpp /props or vLLM max_model_len, or unreachable). "
796
+ f"Your effective cap is the smaller of drydock's {limit:,} and the "
797
+ "server's own -c/--ctx-size.")
798
+ elif n_ctx < limit:
799
+ msg = (f"⚠ Model server reports n_ctx = {n_ctx:,} tokens — SMALLER than "
800
+ f"drydock's {limit:,}. The SERVER is your real cap (you'll stick near "
801
+ f"{n_ctx // 1024}k). Restart it with a larger -c/--ctx-size/max_model_len, "
802
+ "or lower /context to match.")
803
+ else:
804
+ msg = (f"✓ Model server reports n_ctx = {n_ctx:,} tokens (≥ drydock's {limit:,}), "
805
+ "so drydock's budget is the effective limit — no server-side cap.")
806
+ self.call_from_thread(self._info, msg)
807
+
784
808
  def _cmd_compact(self) -> None:
785
809
  """Manually compact the conversation to reclaim context NOW, without
786
810
  waiting for the automatic 60%-of-window threshold (agent.maybe_compact).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.75
3
+ Version: 3.0.76
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -79,6 +79,7 @@ tests/test_rmf.py
79
79
  tests/test_rmf_graph.py
80
80
  tests/test_rmf_stig_graph.py
81
81
  tests/test_runaway_repetition.py
82
+ tests/test_server_probe.py
82
83
  tests/test_skills.py
83
84
  tests/test_stig.py
84
85
  tests/test_stop.py
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
  # PyPI distribution name is drydock-cli (the established install name, continued
8
8
  # from the retired v2 fork); the import package + CLI command stay `drydock`.
9
9
  name = "drydock-cli"
10
- version = "3.0.75"
10
+ version = "3.0.76"
11
11
  description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
@@ -0,0 +1,39 @@
1
+ """Server context-window probe — the definitive 'what's capping me' diagnostic."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+
6
+ from drydock import providers
7
+
8
+
9
+ class _Resp:
10
+ def __init__(self, body): self._b = json.dumps(body).encode()
11
+ def read(self): return self._b
12
+ def __enter__(self): return self
13
+ def __exit__(self, *a): return False
14
+
15
+
16
+ def test_probe_llamacpp_props_n_ctx(monkeypatch):
17
+ import urllib.request
18
+ def fake(url, timeout=0):
19
+ assert url.endswith("/props") # llama.cpp endpoint, /v1 stripped
20
+ return _Resp({"default_generation_settings": {"n_ctx": 32768}})
21
+ monkeypatch.setattr(urllib.request, "urlopen", fake)
22
+ assert providers.probe_server_context("http://localhost:8000/v1") == 32768
23
+
24
+
25
+ def test_probe_vllm_max_model_len(monkeypatch):
26
+ import urllib.request
27
+ def fake(url, timeout=0):
28
+ if url.endswith("/props"):
29
+ raise OSError("no props on vLLM")
30
+ return _Resp({"data": [{"id": "m", "max_model_len": 65536}]})
31
+ monkeypatch.setattr(urllib.request, "urlopen", fake)
32
+ assert providers.probe_server_context("http://localhost:8000/v1") == 65536
33
+
34
+
35
+ def test_probe_unreachable_is_none(monkeypatch):
36
+ import urllib.request
37
+ def boom(url, timeout=0): raise OSError("offline")
38
+ monkeypatch.setattr(urllib.request, "urlopen", boom)
39
+ assert providers.probe_server_context("http://localhost:9999/v1") is None
File without changes
File without changes
File without changes
File without changes