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.
- {drydock_cli-3.0.75/drydock_cli.egg-info → drydock_cli-3.0.76}/PKG-INFO +1 -1
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/providers.py +30 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/app.py +28 -4
- {drydock_cli-3.0.75 → drydock_cli-3.0.76/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/SOURCES.txt +1 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/pyproject.toml +1 -1
- drydock_cli-3.0.76/tests/test_server_probe.py +39 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/LICENSE +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/NOTICE +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/README.md +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/agent.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/cci.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/cli.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/config.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/detect.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/extract.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/guards.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/skills.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/stig.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock/web.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/setup.cfg +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_config.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.75 → drydock_cli-3.0.76}/tests/test_xccdf.py +0 -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
|
-
"
|
|
764
|
-
"
|
|
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).
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|