drydock-cli 3.0.94__tar.gz → 3.0.96__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 (110) hide show
  1. {drydock_cli-3.0.94/drydock_cli.egg-info → drydock_cli-3.0.96}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tools/__init__.py +30 -1
  3. {drydock_cli-3.0.94 → drydock_cli-3.0.96/drydock_cli.egg-info}/PKG-INFO +1 -1
  4. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock_cli.egg-info/SOURCES.txt +1 -0
  5. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/pyproject.toml +1 -1
  6. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_background.py +20 -0
  7. drydock_cli-3.0.96/tests/test_bash_timeout_param.py +30 -0
  8. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/LICENSE +0 -0
  9. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/NOTICE +0 -0
  10. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/README.md +0 -0
  11. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/__init__.py +0 -0
  12. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/__main__.py +0 -0
  13. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/advisor.py +0 -0
  14. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/agent.py +0 -0
  15. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/bash_safety.py +0 -0
  16. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/builtin_skills/__init__.py +0 -0
  17. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/builtin_skills/rmf-categorize.md +0 -0
  18. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/builtin_skills/rmf-control.md +0 -0
  19. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/builtin_skills/rmf-poam.md +0 -0
  20. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/builtin_skills/rmf-review.md +0 -0
  21. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/builtin_skills/stig-assess.md +0 -0
  22. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/builtin_skills/stig-remediate.md +0 -0
  23. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/cci.py +0 -0
  24. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/cli.py +0 -0
  25. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/compaction.py +0 -0
  26. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/config.py +0 -0
  27. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/detect.py +0 -0
  28. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/extract.py +0 -0
  29. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/gittools.py +0 -0
  30. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/graphrag.py +0 -0
  31. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/guards.py +0 -0
  32. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/loop_detect.py +0 -0
  33. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/mcp.py +0 -0
  34. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/poam.py +0 -0
  35. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/providers.py +0 -0
  36. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/rmf.py +0 -0
  37. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/rmf_graph.py +0 -0
  38. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/skills.py +0 -0
  39. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/stig.py +0 -0
  40. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tool_registry.py +0 -0
  41. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tui/__init__.py +0 -0
  42. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tui/app.py +0 -0
  43. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tui/approval.py +0 -0
  44. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tui/messages.py +0 -0
  45. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tui/widgets.py +0 -0
  46. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/tuning.py +0 -0
  47. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock/web.py +0 -0
  48. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock_cli.egg-info/dependency_links.txt +0 -0
  49. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock_cli.egg-info/entry_points.txt +0 -0
  50. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock_cli.egg-info/requires.txt +0 -0
  51. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/drydock_cli.egg-info/top_level.txt +0 -0
  52. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/setup.cfg +0 -0
  53. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_advisor.py +0 -0
  54. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_approval.py +0 -0
  55. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_back_command.py +0 -0
  56. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_binary_output.py +0 -0
  57. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_output_bounding.py +0 -0
  58. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_process_group.py +0 -0
  59. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_safety.py +0 -0
  60. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_sanitize.py +0 -0
  61. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_shell.py +0 -0
  62. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_stdin.py +0 -0
  63. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_bash_timeout_network.py +0 -0
  64. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_cci.py +0 -0
  65. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_cli_agents.py +0 -0
  66. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_compact_command.py +0 -0
  67. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_compaction.py +0 -0
  68. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_config.py +0 -0
  69. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_config_migration.py +0 -0
  70. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_context_limit_config.py +0 -0
  71. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_detect.py +0 -0
  72. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_dispatch.py +0 -0
  73. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_e2e_connected.py +0 -0
  74. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_empty_response.py +0 -0
  75. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_extract.py +0 -0
  76. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_failure_loop.py +0 -0
  77. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_first_run_setup.py +0 -0
  78. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_gittools.py +0 -0
  79. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_graphify_example.py +0 -0
  80. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_graphrag.py +0 -0
  81. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_guards_and_tools.py +0 -0
  82. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_hallucinated_tools.py +0 -0
  83. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_leaked_tool_call.py +0 -0
  84. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_loop_detect.py +0 -0
  85. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_mcp.py +0 -0
  86. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_oneshot_unreachable.py +0 -0
  87. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_plan_autocontinue.py +0 -0
  88. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_poam.py +0 -0
  89. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_providers_unreachable.py +0 -0
  90. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_read_index.py +0 -0
  91. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_rmf.py +0 -0
  92. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_rmf_graph.py +0 -0
  93. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_rmf_stig_graph.py +0 -0
  94. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_runaway_repetition.py +0 -0
  95. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_server_probe.py +0 -0
  96. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_skills.py +0 -0
  97. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_stig.py +0 -0
  98. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_stop.py +0 -0
  99. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_streaming_newlines.py +0 -0
  100. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_subagent.py +0 -0
  101. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_system_prompt_help.py +0 -0
  102. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_todo.py +0 -0
  103. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_tool_arg_parsing.py +0 -0
  104. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_tools_undo.py +0 -0
  105. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_tui.py +0 -0
  106. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_tuning.py +0 -0
  107. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_viewimage.py +0 -0
  108. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_vision_input.py +0 -0
  109. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/tests/test_web_tools.py +0 -0
  110. {drydock_cli-3.0.94 → drydock_cli-3.0.96}/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.94
3
+ Version: 3.0.96
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
@@ -21,6 +21,23 @@ from pathlib import Path
21
21
  # to gigabytes within the timeout. We stream with a byte cap and kill the command
22
22
  # once it's hit — bounding RAM regardless of how much it tries to produce.
23
23
  _MAX_BASH_OUTPUT_BYTES = 256 * 1024 # 256 KB — plenty of context, safe for RAM
24
+ _PARTIAL_TAIL = 4000 # chars of pre-timeout output to keep (tail) in the timeout msg
25
+ _DEFAULT_TIMEOUT = 120
26
+ _MAX_TIMEOUT = 1800 # 30 min hard ceiling — a single command shouldn't hang longer
27
+
28
+
29
+ def _coerce_timeout(value) -> int:
30
+ """Make the model-supplied timeout robust: local models often send it as a
31
+ string ("10"), and a 0/negative/absurd value would make EVERY command time
32
+ out instantly (or hang for hours). Coerce to int, fall back to the default on
33
+ junk, and clamp to (0, _MAX_TIMEOUT]."""
34
+ try:
35
+ t = int(float(value))
36
+ except (TypeError, ValueError):
37
+ return _DEFAULT_TIMEOUT
38
+ if t <= 0:
39
+ return _DEFAULT_TIMEOUT
40
+ return min(t, _MAX_TIMEOUT)
24
41
 
25
42
 
26
43
  def _detect_bash() -> str | None:
@@ -915,7 +932,7 @@ def tool_bash(params: dict, config: dict) -> str:
915
932
  # and crack hashes — a 30s wall killed legitimate long work before it could
916
933
  # finish (terminal-bench: 16 tasks died at 30s on builds/training). Bounded,
917
934
  # and the model can pass a larger `timeout` for genuinely heavy commands.
918
- timeout = params.get("timeout", 120)
935
+ timeout = _coerce_timeout(params.get("timeout", 120))
919
936
  reason = bash_safety.dangerous_command(cmd)
920
937
  if reason is not None:
921
938
  return bash_safety.refusal_message(cmd, reason)
@@ -1020,6 +1037,18 @@ def tool_bash(params: dict, config: dict) -> str:
1020
1037
  )
1021
1038
  if _is_network_command(cmd):
1022
1039
  msg += _OFFLINE_HINT
1040
+ # Preserve any output produced BEFORE the hang — a test run or
1041
+ # build that prints results/diagnostics then stalls would
1042
+ # otherwise lose exactly what the agent needs. Tail-bounded so a
1043
+ # big partial can't bloat context.
1044
+ partial = _sanitize_bash_output(
1045
+ _collapse_repeated_lines("".join(list(chunks)))
1046
+ ).rstrip()
1047
+ if partial:
1048
+ tail = partial[-_PARTIAL_TAIL:]
1049
+ if len(partial) > _PARTIAL_TAIL:
1050
+ tail = "[... earlier output truncated ...]\n" + tail
1051
+ msg += f"\n\n--- output before timeout ---\n{tail}"
1023
1052
  return msg
1024
1053
  if not backgrounded:
1025
1054
  proc.wait()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.94
3
+ Version: 3.0.96
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
@@ -58,6 +58,7 @@ tests/test_bash_sanitize.py
58
58
  tests/test_bash_shell.py
59
59
  tests/test_bash_stdin.py
60
60
  tests/test_bash_timeout_network.py
61
+ tests/test_bash_timeout_param.py
61
62
  tests/test_cci.py
62
63
  tests/test_cli_agents.py
63
64
  tests/test_compact_command.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.94"
10
+ version = "3.0.96"
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"
@@ -42,3 +42,23 @@ def test_foreground_hang_still_times_out():
42
42
  def test_failing_command_still_reports_exit_code():
43
43
  _, out = _timed("false")
44
44
  assert "[exit code: 1]" in out
45
+
46
+
47
+ def test_timeout_preserves_partial_output():
48
+ """A command that prints useful output then hangs must return that output
49
+ alongside the timeout message — not discard it (test runs/builds that stall
50
+ after printing results/diagnostics)."""
51
+ from drydock.tools import tool_bash
52
+ out = tool_bash(
53
+ {"command": "echo IMPORTANT_RESULT; echo line2; sleep 30", "timeout": 3},
54
+ {"cwd": "/tmp"},
55
+ )
56
+ assert "timed out" in out
57
+ assert "IMPORTANT_RESULT" in out and "line2" in out
58
+ assert "output before timeout" in out
59
+
60
+
61
+ def test_timeout_no_output_stays_clean():
62
+ from drydock.tools import tool_bash
63
+ out = tool_bash({"command": "sleep 30", "timeout": 2}, {"cwd": "/tmp"})
64
+ assert "timed out" in out and "output before timeout" not in out
@@ -0,0 +1,30 @@
1
+ """The model-supplied `timeout` must be robust. Local models often send it as a
2
+ string ("10"); a 0/negative value made EVERY command time out instantly; an
3
+ absurd value could hang for hours. _coerce_timeout normalizes all of these."""
4
+ from __future__ import annotations
5
+
6
+ import time
7
+
8
+ import pytest
9
+
10
+ from drydock.tools import _coerce_timeout, tool_bash
11
+
12
+
13
+ @pytest.mark.parametrize("raw,expected", [
14
+ ("10", 10), (30, 30), (15.7, 15), # numbers / stringified / float
15
+ (0, 120), (-5, 120), ("abc", 120), (None, 120), # invalid → default
16
+ (99999, 1800), (100000, 1800), # clamp to the 30-min ceiling
17
+ ])
18
+ def test_coerce_timeout(raw, expected):
19
+ assert _coerce_timeout(raw) == expected
20
+
21
+
22
+ def test_string_timeout_runs_instead_of_crashing():
23
+ # previously raised TypeError in the '>' comparison
24
+ out = tool_bash({"command": "echo hi", "timeout": "10"}, {"cwd": "/tmp"})
25
+ assert "hi" in out
26
+
27
+
28
+ def test_zero_timeout_does_not_instant_timeout():
29
+ out = tool_bash({"command": "echo hi", "timeout": 0}, {"cwd": "/tmp"})
30
+ assert "hi" in out and "timed out" not in out
File without changes
File without changes
File without changes
File without changes