drydock-cli 3.0.106__tar.gz → 3.0.107__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 (120) hide show
  1. {drydock_cli-3.0.106/drydock_cli.egg-info → drydock_cli-3.0.107}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/agent.py +31 -8
  3. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tuning.py +8 -0
  4. {drydock_cli-3.0.106 → drydock_cli-3.0.107/drydock_cli.egg-info}/PKG-INFO +1 -1
  5. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock_cli.egg-info/SOURCES.txt +1 -0
  6. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/pyproject.toml +1 -1
  7. drydock_cli-3.0.107/tests/test_overthink_interrupt.py +47 -0
  8. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/LICENSE +0 -0
  9. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/NOTICE +0 -0
  10. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/README.md +0 -0
  11. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/__init__.py +0 -0
  12. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/__main__.py +0 -0
  13. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/advisor.py +0 -0
  14. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/bash_safety.py +0 -0
  15. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/builtin_skills/__init__.py +0 -0
  16. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/builtin_skills/rmf-categorize.md +0 -0
  17. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/builtin_skills/rmf-control.md +0 -0
  18. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/builtin_skills/rmf-poam.md +0 -0
  19. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/builtin_skills/rmf-review.md +0 -0
  20. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/builtin_skills/stig-assess.md +0 -0
  21. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/builtin_skills/stig-remediate.md +0 -0
  22. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/cci.py +0 -0
  23. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/cli.py +0 -0
  24. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/compaction.py +0 -0
  25. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/config.py +0 -0
  26. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/detect.py +0 -0
  27. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/extract.py +0 -0
  28. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/gittools.py +0 -0
  29. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/graphrag.py +0 -0
  30. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/guards.py +0 -0
  31. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/loop_detect.py +0 -0
  32. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/mcp.py +0 -0
  33. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/poam.py +0 -0
  34. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/providers.py +0 -0
  35. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/rmf.py +0 -0
  36. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/rmf_graph.py +0 -0
  37. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/skills.py +0 -0
  38. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/stig.py +0 -0
  39. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tool_registry.py +0 -0
  40. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tools/__init__.py +0 -0
  41. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tui/__init__.py +0 -0
  42. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tui/app.py +0 -0
  43. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tui/approval.py +0 -0
  44. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tui/messages.py +0 -0
  45. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/tui/widgets.py +0 -0
  46. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock/web.py +0 -0
  47. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock_cli.egg-info/dependency_links.txt +0 -0
  48. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock_cli.egg-info/entry_points.txt +0 -0
  49. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock_cli.egg-info/requires.txt +0 -0
  50. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/drydock_cli.egg-info/top_level.txt +0 -0
  51. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/setup.cfg +0 -0
  52. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_advisor.py +0 -0
  53. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_approval.py +0 -0
  54. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_back_command.py +0 -0
  55. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_background.py +0 -0
  56. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_binary_output.py +0 -0
  57. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_crossplatform.py +0 -0
  58. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_output_bounding.py +0 -0
  59. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_process_group.py +0 -0
  60. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_safety.py +0 -0
  61. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_sanitize.py +0 -0
  62. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_shell.py +0 -0
  63. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_stdin.py +0 -0
  64. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_stop_partial.py +0 -0
  65. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_timeout_network.py +0 -0
  66. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_bash_timeout_param.py +0 -0
  67. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_cci.py +0 -0
  68. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_cli_agents.py +0 -0
  69. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_compact_command.py +0 -0
  70. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_compaction.py +0 -0
  71. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_config.py +0 -0
  72. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_config_migration.py +0 -0
  73. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_context_limit_config.py +0 -0
  74. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_detect.py +0 -0
  75. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_dispatch.py +0 -0
  76. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_e2e_connected.py +0 -0
  77. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_edit_replace_all.py +0 -0
  78. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_empty_response.py +0 -0
  79. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_extract.py +0 -0
  80. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_failure_loop.py +0 -0
  81. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_first_run_setup.py +0 -0
  82. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_gittools.py +0 -0
  83. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_glob_edit_edges.py +0 -0
  84. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_graphify_example.py +0 -0
  85. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_graphrag.py +0 -0
  86. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_grep_and_read_robust.py +0 -0
  87. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_guards_and_tools.py +0 -0
  88. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_hallucinated_tools.py +0 -0
  89. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_leaked_tool_call.py +0 -0
  90. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_loop_detect.py +0 -0
  91. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_mcp.py +0 -0
  92. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_oneshot_unreachable.py +0 -0
  93. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_plan_autocontinue.py +0 -0
  94. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_poam.py +0 -0
  95. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_providers_unreachable.py +0 -0
  96. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_read_index.py +0 -0
  97. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_rmf.py +0 -0
  98. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_rmf_graph.py +0 -0
  99. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_rmf_stig_graph.py +0 -0
  100. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_runaway_repetition.py +0 -0
  101. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_server_probe.py +0 -0
  102. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_skills.py +0 -0
  103. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_stall_retry.py +0 -0
  104. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_stig.py +0 -0
  105. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_stop.py +0 -0
  106. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_streaming_newlines.py +0 -0
  107. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_subagent.py +0 -0
  108. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_system_prompt_help.py +0 -0
  109. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_todo.py +0 -0
  110. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_tool_arg_coercion.py +0 -0
  111. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_tool_arg_coercion_more.py +0 -0
  112. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_tool_arg_parsing.py +0 -0
  113. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_tools_undo.py +0 -0
  114. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_tui.py +0 -0
  115. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_tuning.py +0 -0
  116. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_viewimage.py +0 -0
  117. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_vision_input.py +0 -0
  118. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_web_tools.py +0 -0
  119. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/tests/test_write_content_coerce.py +0 -0
  120. {drydock_cli-3.0.106 → drydock_cli-3.0.107}/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.106
3
+ Version: 3.0.107
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
@@ -13,6 +13,20 @@ from typing import Generator
13
13
  # stall-loop while letting a long, productive plan run as far as it needs.
14
14
  PLAN_CONTINUE_CAP = 3
15
15
 
16
+ # Over-think interrupt: when a call keeps stalling/over-thinking (repeated
17
+ # StallRetry), re-issue in "decisive mode" — a forcing suffix + a hard token cap
18
+ # so the model physically cannot burn thousands of reasoning tokens without
19
+ # acting. The model that produced this loop (gemma) generates 5-15 min / 5k+
20
+ # token "thinking" turns that stall_retry alone can't break (tokens flow); this
21
+ # forces a short, single-action turn instead.
22
+ _DECISIVE_MAX_TOKENS = 1500
23
+ _DECISIVE_SUFFIX = (
24
+ "\n\nURGENT — you have spent too long reasoning without acting. STOP "
25
+ "analyzing. Your reply must be SHORT and take exactly ONE concrete action "
26
+ "right now: emit a single tool call (run a command or write a file) with "
27
+ "little or no explanation. Do NOT lay out a plan; act."
28
+ )
29
+
16
30
 
17
31
  def _plan_has_unfinished(config: dict) -> bool:
18
32
  """True if the model laid out a `todo` plan that still has non-done items."""
@@ -160,6 +174,7 @@ def run(
160
174
  # Stream from LLM — with retry on context-length 400 error
161
175
  retries = 0
162
176
  stall_retries = 0
177
+ decisive = False # over-think interrupt: force a short, single-action turn
163
178
  while retries < 2:
164
179
  try:
165
180
  available = schemas()
@@ -167,7 +182,7 @@ def run(
167
182
  available = [s for s in available if s.get("name") in allow]
168
183
  for event in stream(
169
184
  model=turn_config["model"],
170
- system=system_prompt,
185
+ system=(system_prompt + _DECISIVE_SUFFIX) if decisive else system_prompt,
171
186
  messages=state.messages,
172
187
  tool_schemas=filter_tool_schemas(available, turn_config.get("model")),
173
188
  config=turn_config,
@@ -180,17 +195,25 @@ def run(
180
195
  assistant_turn = event
181
196
  break # success
182
197
  except StallRetry:
183
- # The local server hung mid-generation (config stall_retry_secs).
184
- # Re-issue the same request — a fresh generation usually isn't
185
- # stalled. Bounded so a truly-dead server can't loop forever; on
186
- # exhaustion end the turn cleanly. Stalls have their OWN budget,
187
- # separate from the context-error `retries`.
198
+ # The call stalled or over-thought past stall_retry_secs. First
199
+ # retry re-issues as-is (handles a transient server hang cheaply);
200
+ # if it KEEPS happening it's over-thinking, so escalate to decisive
201
+ # mode a forcing suffix + a hard token cap that make a long
202
+ # reasoning turn impossible. Bounded; on exhaustion end cleanly.
188
203
  stall_retries += 1
189
204
  if stall_retries > 3:
190
- yield TextChunk("\n[model server kept stalling — giving up on this step.]\n")
205
+ yield TextChunk("\n[model kept stalling/over-thinking — giving up on this step.]\n")
191
206
  assistant_turn = None
192
207
  break
193
- yield TextChunk("\n[model server stalled retrying...]\n")
208
+ if stall_retries >= 2 and not decisive:
209
+ decisive = True
210
+ turn_config = dict(turn_config)
211
+ cur = int(turn_config.get("max_tokens", 8192) or 8192)
212
+ turn_config["max_tokens"] = min(cur, _DECISIVE_MAX_TOKENS)
213
+ turn_config["reasoning_effort"] = "low"
214
+ yield TextChunk("\n[taking too long — forcing a decisive, single-action step...]\n")
215
+ else:
216
+ yield TextChunk("\n[model server stalled — retrying...]\n")
194
217
  continue
195
218
  except Exception as e:
196
219
  err = str(e)
@@ -97,6 +97,10 @@ _DEFAULT_SYSTEM_PROMPT = (
97
97
  "RUN it and fix failures until it passes before you stop. Prefer acting — write "
98
98
  "files and run commands — over long planning; don't produce a big plan without "
99
99
  "taking a concrete step.\n"
100
+ "A script you write must run with ONLY what is already installed — prefer "
101
+ "the standard library or shelling out to the task's own tools (openssl, git, "
102
+ "sqlite3); do NOT depend on a package you just pip-installed unless the task "
103
+ "explicitly says to (the grader runs in a clean environment).\n"
100
104
  "You have VISION: when the user references an image file path (.png/.jpg/"
101
105
  ".jpeg/.gif/.webp/.bmp on disk — a screenshot, mockup, diagram, or photo), it "
102
106
  "is attached and you can SEE it; you can also call the `ViewImage` tool to "
@@ -133,6 +137,10 @@ _GEMMA_SYSTEM_PROMPT = (
133
137
  "RUN it and fix failures until it passes before you stop. Prefer acting — write "
134
138
  "files and run commands — over long planning; don't produce a big plan without "
135
139
  "taking a concrete step.\n"
140
+ "A script you write must run with ONLY what is already installed — prefer "
141
+ "the standard library or shelling out to the task's own tools (openssl, git, "
142
+ "sqlite3); do NOT depend on a package you just pip-installed unless the task "
143
+ "explicitly says to (the grader runs in a clean environment).\n"
136
144
  "You have VISION: when the user's message references an image file path "
137
145
  "(.png/.jpg/.jpeg/.gif/.webp/.bmp that exists on disk — a screenshot, mockup, "
138
146
  "diagram, or photo), that image is attached and you can SEE it. You can also "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.106
3
+ Version: 3.0.107
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
@@ -87,6 +87,7 @@ tests/test_leaked_tool_call.py
87
87
  tests/test_loop_detect.py
88
88
  tests/test_mcp.py
89
89
  tests/test_oneshot_unreachable.py
90
+ tests/test_overthink_interrupt.py
90
91
  tests/test_plan_autocontinue.py
91
92
  tests/test_poam.py
92
93
  tests/test_providers_unreachable.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.106"
10
+ version = "3.0.107"
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,47 @@
1
+ """Over-think interrupt: when a call keeps stalling/over-thinking (repeated
2
+ StallRetry), the agent escalates to 'decisive mode' — a forcing suffix on the
3
+ system prompt + a hard max_tokens cap — so the model can't burn a 5k-token
4
+ reasoning turn without acting. First retry re-issues as-is; 2nd+ goes decisive."""
5
+ from __future__ import annotations
6
+
7
+ from drydock import agent
8
+ from drydock.agent import AgentState, _DECISIVE_SUFFIX, _DECISIVE_MAX_TOKENS
9
+ from drydock.providers import StallRetry, AssistantTurn
10
+
11
+
12
+ def _drive(monkeypatch, stalls):
13
+ calls = []
14
+
15
+ def fake_stream(model, system, messages, tool_schemas, config):
16
+ calls.append({"system": system, "max_tokens": config.get("max_tokens"),
17
+ "effort": config.get("reasoning_effort")})
18
+ if len(calls) <= stalls:
19
+ raise StallRetry(1)
20
+ yield AssistantTurn(text="done", tool_calls=[], input_tokens=10, output_tokens=5)
21
+
22
+ monkeypatch.setattr(agent, "stream", fake_stream)
23
+ state = AgentState()
24
+ cfg = {"model": "gemma4", "max_tokens": 8192, "stall_retry_secs": 1, "context_limit": 65536}
25
+ list(agent.run("do it", state, cfg, "SYS"))
26
+ return calls
27
+
28
+
29
+ def test_first_retry_not_decisive_second_is(monkeypatch):
30
+ calls = _drive(monkeypatch, stalls=2) # stall twice, succeed on 3rd
31
+ assert len(calls) == 3
32
+ assert _DECISIVE_SUFFIX not in calls[0]["system"] # attempt 1: as-is
33
+ assert _DECISIVE_SUFFIX not in calls[1]["system"] # attempt 2 (retry 1): as-is
34
+ assert _DECISIVE_SUFFIX in calls[2]["system"] # attempt 3 (retry 2): decisive
35
+ assert calls[2]["max_tokens"] <= _DECISIVE_MAX_TOKENS # hard token cap
36
+ assert calls[2]["effort"] == "low"
37
+
38
+
39
+ def test_gives_up_after_bounded_retries(monkeypatch):
40
+ # stalls forever → bounded (>3) → ends cleanly, doesn't loop forever
41
+ calls = _drive(monkeypatch, stalls=99)
42
+ assert len(calls) <= 5 # 1 initial + up to ~3 retries then give up
43
+
44
+
45
+ def test_no_stall_no_decisive(monkeypatch):
46
+ calls = _drive(monkeypatch, stalls=0) # succeeds first try
47
+ assert len(calls) == 1 and _DECISIVE_SUFFIX not in calls[0]["system"]
File without changes
File without changes
File without changes
File without changes