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