drydock-cli 3.0.87__tar.gz → 3.0.89__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.87/drydock_cli.egg-info → drydock_cli-3.0.89}/PKG-INFO +1 -1
  2. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tools/__init__.py +21 -4
  3. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tuning.py +7 -3
  4. {drydock_cli-3.0.87 → drydock_cli-3.0.89/drydock_cli.egg-info}/PKG-INFO +1 -1
  5. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock_cli.egg-info/SOURCES.txt +1 -0
  6. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/pyproject.toml +1 -1
  7. drydock_cli-3.0.89/tests/test_bash_background.py +44 -0
  8. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_viewimage.py +12 -0
  9. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/LICENSE +0 -0
  10. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/NOTICE +0 -0
  11. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/README.md +0 -0
  12. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/__init__.py +0 -0
  13. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/__main__.py +0 -0
  14. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/advisor.py +0 -0
  15. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/agent.py +0 -0
  16. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/bash_safety.py +0 -0
  17. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/builtin_skills/__init__.py +0 -0
  18. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/builtin_skills/rmf-categorize.md +0 -0
  19. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/builtin_skills/rmf-control.md +0 -0
  20. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/builtin_skills/rmf-poam.md +0 -0
  21. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/builtin_skills/rmf-review.md +0 -0
  22. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/builtin_skills/stig-assess.md +0 -0
  23. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/builtin_skills/stig-remediate.md +0 -0
  24. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/cci.py +0 -0
  25. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/cli.py +0 -0
  26. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/compaction.py +0 -0
  27. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/config.py +0 -0
  28. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/detect.py +0 -0
  29. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/extract.py +0 -0
  30. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/gittools.py +0 -0
  31. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/graphrag.py +0 -0
  32. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/guards.py +0 -0
  33. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/loop_detect.py +0 -0
  34. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/mcp.py +0 -0
  35. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/poam.py +0 -0
  36. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/providers.py +0 -0
  37. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/rmf.py +0 -0
  38. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/rmf_graph.py +0 -0
  39. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/skills.py +0 -0
  40. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/stig.py +0 -0
  41. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tool_registry.py +0 -0
  42. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tui/__init__.py +0 -0
  43. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tui/app.py +0 -0
  44. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tui/approval.py +0 -0
  45. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tui/messages.py +0 -0
  46. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/tui/widgets.py +0 -0
  47. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock/web.py +0 -0
  48. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock_cli.egg-info/dependency_links.txt +0 -0
  49. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock_cli.egg-info/entry_points.txt +0 -0
  50. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock_cli.egg-info/requires.txt +0 -0
  51. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/drydock_cli.egg-info/top_level.txt +0 -0
  52. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/setup.cfg +0 -0
  53. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_advisor.py +0 -0
  54. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_approval.py +0 -0
  55. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_back_command.py +0 -0
  56. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_bash_output_bounding.py +0 -0
  57. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_bash_process_group.py +0 -0
  58. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_bash_safety.py +0 -0
  59. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_bash_timeout_network.py +0 -0
  60. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_cci.py +0 -0
  61. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_cli_agents.py +0 -0
  62. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_compact_command.py +0 -0
  63. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_compaction.py +0 -0
  64. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_config.py +0 -0
  65. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_config_migration.py +0 -0
  66. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_context_limit_config.py +0 -0
  67. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_detect.py +0 -0
  68. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_dispatch.py +0 -0
  69. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_e2e_connected.py +0 -0
  70. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_empty_response.py +0 -0
  71. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_extract.py +0 -0
  72. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_failure_loop.py +0 -0
  73. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_first_run_setup.py +0 -0
  74. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_gittools.py +0 -0
  75. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_graphrag.py +0 -0
  76. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_guards_and_tools.py +0 -0
  77. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_hallucinated_tools.py +0 -0
  78. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_leaked_tool_call.py +0 -0
  79. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_loop_detect.py +0 -0
  80. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_mcp.py +0 -0
  81. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_oneshot_unreachable.py +0 -0
  82. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_plan_autocontinue.py +0 -0
  83. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_poam.py +0 -0
  84. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_providers_unreachable.py +0 -0
  85. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_read_index.py +0 -0
  86. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_rmf.py +0 -0
  87. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_rmf_graph.py +0 -0
  88. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_rmf_stig_graph.py +0 -0
  89. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_runaway_repetition.py +0 -0
  90. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_server_probe.py +0 -0
  91. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_skills.py +0 -0
  92. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_stig.py +0 -0
  93. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_stop.py +0 -0
  94. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_streaming_newlines.py +0 -0
  95. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_subagent.py +0 -0
  96. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_system_prompt_help.py +0 -0
  97. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_todo.py +0 -0
  98. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_tool_arg_parsing.py +0 -0
  99. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_tools_undo.py +0 -0
  100. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_tui.py +0 -0
  101. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_tuning.py +0 -0
  102. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_vision_input.py +0 -0
  103. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/tests/test_web_tools.py +0 -0
  104. {drydock_cli-3.0.87 → drydock_cli-3.0.89}/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.87
3
+ Version: 3.0.89
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
@@ -85,7 +85,7 @@ SCHEMAS = [
85
85
  },
86
86
  {
87
87
  "name": "ViewImage",
88
- "description": "Look at an image file with your vision — use this when you need to SEE a screenshot, mockup, diagram, photo, or rendered output (.png/.jpg/.jpeg/.gif/.webp/.bmp). The image is shown to you so you can describe it, read text from it, or debug it. (Reading an image with the Read tool gives binary garbage; use ViewImage instead.)",
88
+ "description": "Look at an image file with your vision — use this when you need to SEE a screenshot, mockup, diagram, photo, or rendered output (.png/.jpg/.jpeg/.gif/.webp/.bmp). The image is shown to you so you can describe it, read text from it, or debug it. To READ TEXT OR DATA out of an image — a scanned document, invoice, receipt, form, or a screenshot with text — use ViewImage FIRST (you can read it directly); prefer it over OCR tools like tesseract/pdftotext, which are often unreliable or not installed. (Reading an image with the Read tool gives binary garbage; use ViewImage instead.)",
89
89
  "input_schema": {
90
90
  "type": "object",
91
91
  "properties": {
@@ -936,6 +936,7 @@ def tool_bash(params: dict, config: dict) -> str:
936
936
  reader = threading.Thread(target=_drain, daemon=True)
937
937
  reader.start()
938
938
  start = time.monotonic()
939
+ backgrounded = False
939
940
  while reader.is_alive():
940
941
  reader.join(0.3)
941
942
  if capped.is_set():
@@ -945,6 +946,17 @@ def tool_bash(params: dict, config: dict) -> str:
945
946
  kill_process_group(proc)
946
947
  proc.wait()
947
948
  return "[stopped by user]"
949
+ # The SHELL has exited but the pipe is still open → the command
950
+ # backgrounded a child (`cmd &`, a server the task wants to keep
951
+ # running) that inherited stdout. Don't wait for it (that would hang
952
+ # until the timeout) and DON'T kill it — return what we have so the
953
+ # background process survives. (Redirecting its output, `cmd >log &`,
954
+ # closes the pipe and never reaches here.)
955
+ if proc.poll() is not None:
956
+ reader.join(0.5) # brief grace for any final buffered output
957
+ if reader.is_alive():
958
+ backgrounded = True
959
+ break
948
960
  if time.monotonic() - start > timeout:
949
961
  kill_process_group(proc)
950
962
  proc.wait()
@@ -958,11 +970,16 @@ def tool_bash(params: dict, config: dict) -> str:
958
970
  if _is_network_command(cmd):
959
971
  msg += _OFFLINE_HINT
960
972
  return msg
961
- proc.wait()
973
+ if not backgrounded:
974
+ proc.wait()
962
975
  # Collapse repetitive runs FIRST (turns 256 KB of "y\n" into ~2 lines),
963
976
  # then note if we hit the byte cap. Bounds both RAM (the cap) and context
964
- # tokens (the collapse).
965
- output = _collapse_repeated_lines("".join(chunks))
977
+ # tokens (the collapse). Snapshot chunks (list()) in case the reader
978
+ # daemon is still appending for a backgrounded child.
979
+ output = _collapse_repeated_lines("".join(list(chunks)))
980
+ if backgrounded:
981
+ return (output.rstrip() + "\n[a process was left running in the background; "
982
+ "the command returned. Check it with a follow-up command.]").lstrip("\n")
966
983
  if capped.is_set():
967
984
  return (
968
985
  output.rstrip()
@@ -95,7 +95,9 @@ _DEFAULT_SYSTEM_PROMPT = (
95
95
  ".jpeg/.gif/.webp/.bmp on disk — a screenshot, mockup, diagram, or photo), it "
96
96
  "is attached and you can SEE it; you can also call the `ViewImage` tool to "
97
97
  "look at any image you find yourself (Read gives binary garbage for images — "
98
- "use ViewImage). Describe it, read text from it, or debug with it — never "
98
+ "use ViewImage). To read text/data out of a scanned document, invoice, "
99
+ "receipt, or screenshot, use ViewImage directly rather than OCR tools like "
100
+ "tesseract. Describe it, read text from it, or debug with it — never "
99
101
  "claim you cannot view images."
100
102
  )
101
103
 
@@ -123,8 +125,10 @@ _GEMMA_SYSTEM_PROMPT = (
123
125
  "(.png/.jpg/.jpeg/.gif/.webp/.bmp that exists on disk — a screenshot, mockup, "
124
126
  "diagram, or photo), that image is attached and you can SEE it. You can also "
125
127
  "call the `ViewImage` tool to look at any image you find yourself (Read gives "
126
- "binary garbage for images — use ViewImage). Describe it, read text from it, "
127
- "or use it to debug never claim you cannot view images."
128
+ "binary garbage for images — use ViewImage). To read TEXT or DATA out of a "
129
+ "scanned document, invoice, receipt, form, or a screenshot, use ViewImage "
130
+ "directly — don't reach for OCR tools like tesseract first. Describe it, read "
131
+ "text from it, or use it to debug — never claim you cannot view images."
128
132
  )
129
133
 
130
134
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.87
3
+ Version: 3.0.89
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
@@ -49,6 +49,7 @@ drydock_cli.egg-info/top_level.txt
49
49
  tests/test_advisor.py
50
50
  tests/test_approval.py
51
51
  tests/test_back_command.py
52
+ tests/test_bash_background.py
52
53
  tests/test_bash_output_bounding.py
53
54
  tests/test_bash_process_group.py
54
55
  tests/test_bash_safety.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.87"
10
+ version = "3.0.89"
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,44 @@
1
+ """tool_bash must support backgrounded processes (`cmd &`) — a server/daemon the
2
+ task wants to keep running — instead of hanging to the timeout then killing it.
3
+ Found via the kv-store-grpc tbench task (needs a background gRPC server)."""
4
+ from __future__ import annotations
5
+
6
+ import subprocess
7
+ import time
8
+
9
+ from drydock.tools import tool_bash
10
+
11
+
12
+ def _timed(cmd, timeout=8):
13
+ t0 = time.monotonic()
14
+ out = tool_bash({"command": cmd, "timeout": timeout}, {"cwd": "/tmp"})
15
+ return time.monotonic() - t0, out
16
+
17
+
18
+ def test_backgrounded_command_returns_fast_and_survives():
19
+ try:
20
+ dt, out = _timed("sleep 12 & echo bg-started")
21
+ assert dt < 3.0, f"backgrounded cmd hung ({dt:.1f}s) — should return promptly"
22
+ assert "background" in out.lower()
23
+ time.sleep(0.5)
24
+ n = subprocess.run("pgrep -f 'sleep 12' | wc -l", shell=True,
25
+ capture_output=True, text=True).stdout.strip()
26
+ assert int(n) >= 1, "backgrounded process was killed (should survive)"
27
+ finally:
28
+ subprocess.run("pkill -f 'sleep 12'", shell=True)
29
+
30
+
31
+ def test_normal_command_unaffected():
32
+ dt, out = _timed("echo hello && echo world")
33
+ assert "hello" in out and "world" in out and dt < 2.0
34
+ assert "background" not in out.lower()
35
+
36
+
37
+ def test_foreground_hang_still_times_out():
38
+ dt, out = _timed("sleep 30", timeout=3)
39
+ assert "timed out" in out and dt < 6.0
40
+
41
+
42
+ def test_failing_command_still_reports_exit_code():
43
+ _, out = _timed("false")
44
+ assert "[exit code: 1]" in out
@@ -74,3 +74,15 @@ def test_image_load_error_classifier():
74
74
  assert is_image_load_error("invalid_request_error: could not decode image")
75
75
  assert not is_image_load_error("rate limit exceeded")
76
76
  assert not is_image_load_error("context_length_exceeded")
77
+
78
+
79
+ def test_viewimage_nudges_over_ocr_for_documents():
80
+ """The tool description + both system prompts steer the model to ViewImage
81
+ (its own vision) for reading text off invoices/receipts/scans, instead of
82
+ reaching for OCR tools first (observed on the financial-doc tbench task)."""
83
+ from drydock.tuning import system_prompt_for_model
84
+ desc = reg.get("ViewImage").schema["description"]
85
+ assert "OCR" in desc and ("invoice" in desc or "receipt" in desc)
86
+ for model in ("gemma4", "some-other-model"):
87
+ p = system_prompt_for_model(model)
88
+ assert "OCR" in p and "ViewImage" in p
File without changes
File without changes
File without changes
File without changes