python-agent-harness 1.5.4.8__tar.gz → 1.5.4.9__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 (92) hide show
  1. {python_agent_harness-1.5.4.8/python_agent_harness.egg-info → python_agent_harness-1.5.4.9}/PKG-INFO +1 -1
  2. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/pyproject.toml +1 -1
  3. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/__init__.py +1 -1
  4. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/client.py +67 -15
  5. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9/python_agent_harness.egg-info}/PKG-INFO +1 -1
  6. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_client.py +1 -0
  7. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/LICENSE +0 -0
  8. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/README.md +0 -0
  9. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/__main__.py +0 -0
  10. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/agent.py +0 -0
  11. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/cli.py +0 -0
  12. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/commands.py +0 -0
  13. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/config.py +0 -0
  14. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/context_manager.py +0 -0
  15. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/diffrender.py +0 -0
  16. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/mcp/__init__.py +0 -0
  17. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/mcp/client.py +0 -0
  18. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/mcp/config.py +0 -0
  19. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/mcp/manager.py +0 -0
  20. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/models.py +0 -0
  21. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/persistence.py +0 -0
  22. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/planmode.py +0 -0
  23. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/agent.md +0 -0
  24. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/build-switch.md +0 -0
  25. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/commands/explain.md +0 -0
  26. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/compact.md +0 -0
  27. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/initialize.md +0 -0
  28. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/plan-mode.md +0 -0
  29. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/plan.md +0 -0
  30. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/review.md +0 -0
  31. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/subagent.md +0 -0
  32. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/summary.md +0 -0
  33. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/task-completion-rules.md +0 -0
  34. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts/title.md +0 -0
  35. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/prompts.py +0 -0
  36. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/session.py +0 -0
  37. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/subagent.py +0 -0
  38. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/token_estimator.py +0 -0
  39. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tool_runner.py +0 -0
  40. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/__init__.py +0 -0
  41. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/agent_tool.py +0 -0
  42. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/base.py +0 -0
  43. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/bash.py +0 -0
  44. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/diffapply.py +0 -0
  45. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/edit.py +0 -0
  46. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/edit_mac.py +0 -0
  47. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/filesystem.py +0 -0
  48. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/glob.py +0 -0
  49. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/glob_mac.py +0 -0
  50. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/grep.py +0 -0
  51. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/grep_mac.py +0 -0
  52. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/insert.py +0 -0
  53. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/mcp.py +0 -0
  54. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/mkdir.py +0 -0
  55. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/planexit.py +0 -0
  56. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/question.py +0 -0
  57. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/read.py +0 -0
  58. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/skill.py +0 -0
  59. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/todo.py +0 -0
  60. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tools/write.py +0 -0
  61. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tui/__init__.py +0 -0
  62. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tui/commands.py +0 -0
  63. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tui/core.py +0 -0
  64. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tui/input.py +0 -0
  65. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness/tui/render.py +0 -0
  66. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness.egg-info/SOURCES.txt +0 -0
  67. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness.egg-info/dependency_links.txt +0 -0
  68. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness.egg-info/entry_points.txt +0 -0
  69. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness.egg-info/requires.txt +0 -0
  70. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/python_agent_harness.egg-info/top_level.txt +0 -0
  71. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/setup.cfg +0 -0
  72. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_agents_md.py +0 -0
  73. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_cli.py +0 -0
  74. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_commands.py +0 -0
  75. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_concurrent_subagents.py +0 -0
  76. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_config.py +0 -0
  77. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_context_rules.py +0 -0
  78. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_diffrender.py +0 -0
  79. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_filesystem.py +0 -0
  80. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_invariants.py +0 -0
  81. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_mcp.py +0 -0
  82. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_models.py +0 -0
  83. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_persistence.py +0 -0
  84. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_planmode.py +0 -0
  85. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_prompts.py +0 -0
  86. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_scenarios.py +0 -0
  87. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_session.py +0 -0
  88. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_subagent.py +0 -0
  89. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_subagent_isolation.py +0 -0
  90. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_todos_scope.py +0 -0
  91. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_token_estimator.py +0 -0
  92. {python_agent_harness-1.5.4.8 → python_agent_harness-1.5.4.9}/tests/test_tools_misc.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-agent-harness
3
- Version: 1.5.4.8
3
+ Version: 1.5.4.9
4
4
  Summary: A lightweight, hackable mini-OpenCode written in Python.
5
5
  Author: huming
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-agent-harness"
7
- version = "1.5.4.8"
7
+ version = "1.5.4.9"
8
8
  description = "A lightweight, hackable mini-OpenCode written in Python."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -5,7 +5,7 @@ from .mcp.manager import MCPManager
5
5
  from .models import AgentMode, Message, ToolCall, ToolSpec
6
6
  from .session import Session
7
7
 
8
- __version__ = "1.5.4.8"
8
+ __version__ = "1.5.4.9"
9
9
 
10
10
  __all__ = [
11
11
  "Session",
@@ -12,6 +12,7 @@ import json
12
12
  import os
13
13
  import random
14
14
  import socket as _socket
15
+ import sys
15
16
  import threading
16
17
  import time
17
18
  import uuid
@@ -822,6 +823,47 @@ def _default_api_key() -> str | None:
822
823
  return os.environ.get("OPENAI_API_KEY") or os.environ.get("DEEPSEEK_API_KEY") or None
823
824
 
824
825
 
826
+ def _extract_socket(conn: object) -> _socket.socket | None:
827
+ """Best-effort extraction of the raw socket from an httpcore connection.
828
+
829
+ For direct (non-proxy) connections the socket lives at
830
+ ``conn._connection._network_stream``. Proxy connections are wrapped
831
+ (ForwardHTTPConnection/TunnelHTTPConnection -> HTTPConnection ->
832
+ HTTP11Connection), so we unwrap up to 4 levels looking for the
833
+ ``_network_stream`` attribute that owns the raw socket.
834
+ """
835
+ try:
836
+ obj: object = conn
837
+ stream = None
838
+ for _ in range(4):
839
+ stream = getattr(obj, "_network_stream", None)
840
+ if stream is not None:
841
+ break
842
+ obj = getattr(obj, "_connection", None)
843
+ if obj is None:
844
+ break
845
+ return stream.get_extra_info("socket") if stream is not None else None
846
+ except Exception: # noqa: BLE001 - best effort
847
+ return None
848
+
849
+
850
+ def _shutdown_and_close(sock: _socket.socket) -> None:
851
+ """Shutdown then close a socket to wake blocked reads.
852
+
853
+ On macOS the socket is in non-blocking mode (httpcore calls
854
+ settimeout for the read timeout), so a blocked recv parks in
855
+ select(); closing the fd before that select has processed the
856
+ shutdown wakeup can lose the wakeup and leave the read parked
857
+ forever. Give the woken thread a moment to observe the EOF first.
858
+ """
859
+ with contextlib.suppress(OSError):
860
+ sock.shutdown(_socket.SHUT_RDWR)
861
+ if sys.platform == "darwin":
862
+ time.sleep(0.05)
863
+ with contextlib.suppress(OSError):
864
+ sock.close()
865
+
866
+
825
867
  def _abort_inflight_sockets(client: httpx.Client) -> None:
826
868
  """Wake any blocked stream reads on every live pool socket.
827
869
 
@@ -849,21 +891,31 @@ def _abort_inflight_sockets(client: httpx.Client) -> None:
849
891
  ``contextlib.suppress`` right after this call, so a double-close is
850
892
  harmless. Every step is best-effort; any failure is ignored.
851
893
  """
852
- pool = getattr(getattr(client, "_transport", None), "_pool", None)
853
- for conn in getattr(pool, "_connections", None) or ():
854
- try:
855
- stream = getattr(getattr(conn, "_connection", None), "_network_stream", None)
856
- sock = stream.get_extra_info("socket") if stream is not None else None
857
- except Exception: # noqa: BLE001 - best effort
858
- sock = None
859
- if sock is not None:
860
- # shutdown first (wakes the recv on Linux without touching
861
- # the fd), then close the fd (the reliable wake on
862
- # macOS/BSD, where shutdown alone may not).
863
- with contextlib.suppress(OSError):
864
- sock.shutdown(_socket.SHUT_RDWR)
865
- with contextlib.suppress(OSError):
866
- sock.close()
894
+ pools: list[object] = []
895
+ base_pool = getattr(getattr(client, "_transport", None), "_pool", None)
896
+ if base_pool is not None:
897
+ pools.append(base_pool)
898
+
899
+ if sys.platform == "darwin":
900
+ # macOS only: with proxy env vars (HTTP_PROXY/HTTPS_PROXY) set,
901
+ # httpx routes requests through proxy transports registered in
902
+ # client._mounts (pools are httpcore.HTTPProxy), leaving the
903
+ # base transport pool above empty. On Linux this never happens
904
+ # in CI and the code path above is sufficient, so this extra
905
+ # walk is deliberately restricted to macOS.
906
+ mounts = getattr(client, "_mounts", None)
907
+ if isinstance(mounts, dict):
908
+ for transport in mounts.values():
909
+ if transport is not None:
910
+ pool = getattr(transport, "_pool", None)
911
+ if pool is not None:
912
+ pools.append(pool)
913
+
914
+ for pool in pools:
915
+ for conn in getattr(pool, "_connections", None) or ():
916
+ sock = _extract_socket(conn)
917
+ if sock is not None:
918
+ _shutdown_and_close(sock)
867
919
 
868
920
 
869
921
  def _iter_sse(lines: Iterator[str]) -> Iterator[str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-agent-harness
3
- Version: 1.5.4.8
3
+ Version: 1.5.4.9
4
4
  Summary: A lightweight, hackable mini-OpenCode written in Python.
5
5
  Author: huming
6
6
  License-Expression: MIT
@@ -816,6 +816,7 @@ class TestAbortInflightSockets(unittest.TestCase):
816
816
 
817
817
  def _fake_client_with_stream(self, stream):
818
818
  conn = mock.Mock()
819
+ conn._network_stream = None
819
820
  conn._connection._network_stream = stream
820
821
  client = mock.Mock()
821
822
  client._transport._pool._connections = [conn]