drydock-cli 3.0.86__tar.gz → 3.0.87__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 (104) hide show
  1. {drydock_cli-3.0.86/drydock_cli.egg-info → drydock_cli-3.0.87}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/agent.py +2 -0
  3. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/compaction.py +9 -2
  4. {drydock_cli-3.0.86 → drydock_cli-3.0.87/drydock_cli.egg-info}/PKG-INFO +1 -1
  5. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/pyproject.toml +1 -1
  6. drydock_cli-3.0.87/tests/test_compact_command.py +91 -0
  7. drydock_cli-3.0.86/tests/test_compact_command.py +0 -47
  8. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/LICENSE +0 -0
  9. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/NOTICE +0 -0
  10. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/README.md +0 -0
  11. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/__init__.py +0 -0
  12. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/__main__.py +0 -0
  13. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/advisor.py +0 -0
  14. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/bash_safety.py +0 -0
  15. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/builtin_skills/__init__.py +0 -0
  16. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/builtin_skills/rmf-categorize.md +0 -0
  17. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/builtin_skills/rmf-control.md +0 -0
  18. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/builtin_skills/rmf-poam.md +0 -0
  19. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/builtin_skills/rmf-review.md +0 -0
  20. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/builtin_skills/stig-assess.md +0 -0
  21. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/builtin_skills/stig-remediate.md +0 -0
  22. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/cci.py +0 -0
  23. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/cli.py +0 -0
  24. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/config.py +0 -0
  25. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/detect.py +0 -0
  26. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/extract.py +0 -0
  27. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/gittools.py +0 -0
  28. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/graphrag.py +0 -0
  29. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/guards.py +0 -0
  30. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/loop_detect.py +0 -0
  31. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/mcp.py +0 -0
  32. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/poam.py +0 -0
  33. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/providers.py +0 -0
  34. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/rmf.py +0 -0
  35. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/rmf_graph.py +0 -0
  36. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/skills.py +0 -0
  37. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/stig.py +0 -0
  38. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tool_registry.py +0 -0
  39. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tools/__init__.py +0 -0
  40. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tui/__init__.py +0 -0
  41. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tui/app.py +0 -0
  42. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tui/approval.py +0 -0
  43. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tui/messages.py +0 -0
  44. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tui/widgets.py +0 -0
  45. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/tuning.py +0 -0
  46. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock/web.py +0 -0
  47. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock_cli.egg-info/SOURCES.txt +0 -0
  48. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock_cli.egg-info/dependency_links.txt +0 -0
  49. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock_cli.egg-info/entry_points.txt +0 -0
  50. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock_cli.egg-info/requires.txt +0 -0
  51. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/drydock_cli.egg-info/top_level.txt +0 -0
  52. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/setup.cfg +0 -0
  53. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_advisor.py +0 -0
  54. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_approval.py +0 -0
  55. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_back_command.py +0 -0
  56. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_bash_output_bounding.py +0 -0
  57. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_bash_process_group.py +0 -0
  58. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_bash_safety.py +0 -0
  59. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_bash_timeout_network.py +0 -0
  60. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_cci.py +0 -0
  61. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_cli_agents.py +0 -0
  62. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_compaction.py +0 -0
  63. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_config.py +0 -0
  64. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_config_migration.py +0 -0
  65. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_context_limit_config.py +0 -0
  66. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_detect.py +0 -0
  67. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_dispatch.py +0 -0
  68. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_e2e_connected.py +0 -0
  69. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_empty_response.py +0 -0
  70. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_extract.py +0 -0
  71. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_failure_loop.py +0 -0
  72. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_first_run_setup.py +0 -0
  73. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_gittools.py +0 -0
  74. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_graphrag.py +0 -0
  75. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_guards_and_tools.py +0 -0
  76. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_hallucinated_tools.py +0 -0
  77. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_leaked_tool_call.py +0 -0
  78. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_loop_detect.py +0 -0
  79. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_mcp.py +0 -0
  80. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_oneshot_unreachable.py +0 -0
  81. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_plan_autocontinue.py +0 -0
  82. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_poam.py +0 -0
  83. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_providers_unreachable.py +0 -0
  84. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_read_index.py +0 -0
  85. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_rmf.py +0 -0
  86. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_rmf_graph.py +0 -0
  87. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_rmf_stig_graph.py +0 -0
  88. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_runaway_repetition.py +0 -0
  89. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_server_probe.py +0 -0
  90. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_skills.py +0 -0
  91. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_stig.py +0 -0
  92. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_stop.py +0 -0
  93. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_streaming_newlines.py +0 -0
  94. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_subagent.py +0 -0
  95. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_system_prompt_help.py +0 -0
  96. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_todo.py +0 -0
  97. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_tool_arg_parsing.py +0 -0
  98. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_tools_undo.py +0 -0
  99. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_tui.py +0 -0
  100. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_tuning.py +0 -0
  101. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_viewimage.py +0 -0
  102. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_vision_input.py +0 -0
  103. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/tests/test_web_tools.py +0 -0
  104. {drydock_cli-3.0.86 → drydock_cli-3.0.87}/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.86
3
+ Version: 3.0.87
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
@@ -63,6 +63,7 @@ class AgentState:
63
63
  messages: list = field(default_factory=list)
64
64
  total_input_tokens: int = 0
65
65
  total_output_tokens: int = 0
66
+ last_input_tokens: int = 0 # prompt tokens the SERVER counted on the last call
66
67
  turn_count: int = 0
67
68
  current_effort: str = "" # "high"/"low" of the in-flight LLM call (for the UI)
68
69
 
@@ -210,6 +211,7 @@ def run(
210
211
 
211
212
  state.total_input_tokens += assistant_turn.input_tokens
212
213
  state.total_output_tokens += assistant_turn.output_tokens
214
+ state.last_input_tokens = assistant_turn.input_tokens
213
215
  yield TurnDone(assistant_turn.input_tokens, assistant_turn.output_tokens)
214
216
 
215
217
  # No tool calls = conversation complete — UNLESS the model emitted a
@@ -192,9 +192,16 @@ def emergency_compact(messages: list, context_limit: int = 131072) -> list:
192
192
 
193
193
 
194
194
  def maybe_compact(state, config: dict) -> None:
195
- """Compact state.messages if approaching context limit."""
195
+ """Compact state.messages if approaching context limit.
196
+
197
+ Uses the MAX of the char-based estimate and the SERVER's real prompt-token
198
+ count from the last call. estimate_tokens (chars/3.5) undercounts token-dense
199
+ content — code, compiler/build output, tracebacks tokenize to ~3 chars/token
200
+ — so on such tasks the real context (what the gauge shows) can pass 60% while
201
+ the estimate is still under it, and compaction fires late. The real count
202
+ catches that."""
196
203
  limit = config.get("context_limit", 131072)
197
- current = estimate_tokens(state.messages)
204
+ current = max(estimate_tokens(state.messages), getattr(state, "last_input_tokens", 0))
198
205
 
199
206
  if current > limit * 0.60:
200
207
  state.messages = compact(state.messages, limit)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.86
3
+ Version: 3.0.87
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
@@ -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.86"
10
+ version = "3.0.87"
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,91 @@
1
+ """The /compact slash command (advertised in /help) must actually compact:
2
+ wire it to compaction.compact and report tokens freed. Regression for the gap
3
+ where /help listed /compact but handle_command never implemented it."""
4
+ from __future__ import annotations
5
+
6
+ from drydock import cli
7
+ from drydock.agent import AgentState
8
+ from drydock.compaction import estimate_tokens
9
+
10
+
11
+ def _state_with_big_tool_result() -> AgentState:
12
+ state = AgentState()
13
+ state.messages = [
14
+ {"role": "user", "content": "do a thing"},
15
+ {
16
+ "role": "assistant",
17
+ "content": "",
18
+ "tool_calls": [{"id": "c1", "name": "Bash", "input": {"command": "cat big"}}],
19
+ },
20
+ {"role": "tool", "tool_call_id": "c1", "content": "Z" * 8000},
21
+ {"role": "assistant", "content": "done"},
22
+ ]
23
+ return state
24
+
25
+
26
+ def test_compact_command_is_handled():
27
+ """/compact returns True (handled), not False (falls through to the agent)."""
28
+ state = AgentState()
29
+ assert cli.handle_command("/compact", state, {"context_limit": 65536}) is True
30
+
31
+
32
+ def test_compact_shrinks_a_bloated_conversation(capsys):
33
+ state = _state_with_big_tool_result()
34
+ before = estimate_tokens(state.messages)
35
+
36
+ cli.handle_command("/compact", state, {"context_limit": 65536})
37
+
38
+ after = estimate_tokens(state.messages)
39
+ assert after < before # the 8000-char tool result was truncated
40
+ out = capsys.readouterr().out.lower()
41
+ assert "compact" in out and "freed" in out
42
+
43
+
44
+ def test_compact_on_empty_conversation_is_a_noop(capsys):
45
+ state = AgentState()
46
+ assert cli.handle_command("/compact", state, {"context_limit": 65536}) is True
47
+ assert "nothing to compact" in capsys.readouterr().out.lower()
48
+
49
+
50
+ def test_maybe_compact_uses_real_token_count_not_just_estimate():
51
+ """maybe_compact must trigger on the SERVER's real prompt-token count, not
52
+ only the chars/3.5 estimate (which undercounts dense code/build output).
53
+ Regression: build task hit 64% real ctx without compacting because the
54
+ estimate read ~55%."""
55
+ from drydock.agent import AgentState
56
+ from drydock.compaction import maybe_compact, estimate_tokens
57
+
58
+ st = AgentState()
59
+ # A few small messages: the char estimate is tiny (well under 60% of 65536)…
60
+ st.messages = [
61
+ {"role": "user", "content": "hi"},
62
+ {"role": "assistant", "content": "hello", "tool_calls": []},
63
+ ]
64
+ assert estimate_tokens(st.messages) < 0.60 * 65536 # estimate says "fine"
65
+ cfg = {"context_limit": 65536}
66
+ # …but the server reported a large real prompt (dense content) → must compact.
67
+ st.last_input_tokens = 45000 # 68% of 65536
68
+ before = list(st.messages)
69
+ maybe_compact(st, cfg)
70
+ # compact() ran (it targets 60%); with only tiny messages it can't shrink, but
71
+ # the point is it was INVOKED on the real count — verify the trigger path by
72
+ # checking a case with droppable content:
73
+ st.messages = before + [
74
+ {"role": "assistant", "content": "", "tool_calls": [{"id": "a", "name": "Bash", "input": {"command": "x"}}]},
75
+ {"role": "tool", "tool_call_id": "a", "name": "Bash", "content": "Z" * 200000}, # huge old tool result
76
+ ]
77
+ st.last_input_tokens = 45000
78
+ maybe_compact(st, cfg)
79
+ # the huge old tool result should be trimmed by compact()
80
+ assert estimate_tokens(st.messages) < estimate_tokens(before + [{"role": "tool", "content": "Z" * 200000}])
81
+
82
+
83
+ def test_maybe_compact_low_real_count_no_trigger():
84
+ from drydock.agent import AgentState
85
+ from drydock.compaction import maybe_compact
86
+ st = AgentState()
87
+ st.messages = [{"role": "user", "content": "small"}]
88
+ st.last_input_tokens = 1000
89
+ before = list(st.messages)
90
+ maybe_compact(st, {"context_limit": 65536})
91
+ assert st.messages == before # nothing to do, unchanged
@@ -1,47 +0,0 @@
1
- """The /compact slash command (advertised in /help) must actually compact:
2
- wire it to compaction.compact and report tokens freed. Regression for the gap
3
- where /help listed /compact but handle_command never implemented it."""
4
- from __future__ import annotations
5
-
6
- from drydock import cli
7
- from drydock.agent import AgentState
8
- from drydock.compaction import estimate_tokens
9
-
10
-
11
- def _state_with_big_tool_result() -> AgentState:
12
- state = AgentState()
13
- state.messages = [
14
- {"role": "user", "content": "do a thing"},
15
- {
16
- "role": "assistant",
17
- "content": "",
18
- "tool_calls": [{"id": "c1", "name": "Bash", "input": {"command": "cat big"}}],
19
- },
20
- {"role": "tool", "tool_call_id": "c1", "content": "Z" * 8000},
21
- {"role": "assistant", "content": "done"},
22
- ]
23
- return state
24
-
25
-
26
- def test_compact_command_is_handled():
27
- """/compact returns True (handled), not False (falls through to the agent)."""
28
- state = AgentState()
29
- assert cli.handle_command("/compact", state, {"context_limit": 65536}) is True
30
-
31
-
32
- def test_compact_shrinks_a_bloated_conversation(capsys):
33
- state = _state_with_big_tool_result()
34
- before = estimate_tokens(state.messages)
35
-
36
- cli.handle_command("/compact", state, {"context_limit": 65536})
37
-
38
- after = estimate_tokens(state.messages)
39
- assert after < before # the 8000-char tool result was truncated
40
- out = capsys.readouterr().out.lower()
41
- assert "compact" in out and "freed" in out
42
-
43
-
44
- def test_compact_on_empty_conversation_is_a_noop(capsys):
45
- state = AgentState()
46
- assert cli.handle_command("/compact", state, {"context_limit": 65536}) is True
47
- assert "nothing to compact" in capsys.readouterr().out.lower()
File without changes
File without changes
File without changes
File without changes