henchman-ai 0.1.8__py3-none-any.whl → 0.1.9__py3-none-any.whl
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.
- henchman/cli/input.py +5 -3
- henchman/config/schema.py +6 -0
- henchman/tools/registry.py +111 -8
- henchman/utils/__init__.py +14 -0
- henchman/utils/retry.py +166 -0
- henchman/version.py +1 -1
- {henchman_ai-0.1.8.dist-info → henchman_ai-0.1.9.dist-info}/METADATA +1 -1
- {henchman_ai-0.1.8.dist-info → henchman_ai-0.1.9.dist-info}/RECORD +11 -10
- {henchman_ai-0.1.8.dist-info → henchman_ai-0.1.9.dist-info}/WHEEL +0 -0
- {henchman_ai-0.1.8.dist-info → henchman_ai-0.1.9.dist-info}/entry_points.txt +0 -0
- {henchman_ai-0.1.8.dist-info → henchman_ai-0.1.9.dist-info}/licenses/LICENSE +0 -0
henchman/cli/input.py
CHANGED
|
@@ -5,6 +5,7 @@ This module handles user input including @ file references and ! shell commands.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import contextlib
|
|
8
9
|
import re
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
@@ -124,9 +125,10 @@ def create_session(
|
|
|
124
125
|
# Clear buffer if there is text
|
|
125
126
|
buffer.text = ""
|
|
126
127
|
else:
|
|
127
|
-
# If buffer is empty,
|
|
128
|
-
#
|
|
129
|
-
|
|
128
|
+
# If buffer is empty, return to prompt with empty result
|
|
129
|
+
# Use suppress to handle case where result is already set
|
|
130
|
+
with contextlib.suppress(Exception):
|
|
131
|
+
event.app.exit(result="")
|
|
130
132
|
|
|
131
133
|
history = FileHistory(str(history_file)) if history_file else None
|
|
132
134
|
|
henchman/config/schema.py
CHANGED
|
@@ -44,6 +44,9 @@ class ToolSettings(BaseModel):
|
|
|
44
44
|
max_tool_calls_per_turn: Maximum tool calls allowed per turn.
|
|
45
45
|
max_protected_ratio: Maximum ratio of context that can be protected.
|
|
46
46
|
adaptive_limits: Whether to adjust limits based on progress detection.
|
|
47
|
+
network_retries: Number of retries for network tools (0 = no retries).
|
|
48
|
+
retry_base_delay: Base delay in seconds for retry backoff.
|
|
49
|
+
retry_max_delay: Maximum delay in seconds for retry backoff.
|
|
47
50
|
"""
|
|
48
51
|
|
|
49
52
|
auto_approve_read: bool = True
|
|
@@ -53,6 +56,9 @@ class ToolSettings(BaseModel):
|
|
|
53
56
|
max_tool_calls_per_turn: int = 100
|
|
54
57
|
max_protected_ratio: float = 0.3
|
|
55
58
|
adaptive_limits: bool = True
|
|
59
|
+
network_retries: int = 3
|
|
60
|
+
retry_base_delay: float = 1.0
|
|
61
|
+
retry_max_delay: float = 30.0
|
|
56
62
|
|
|
57
63
|
|
|
58
64
|
class UISettings(BaseModel):
|
henchman/tools/registry.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Tool registry for managing and executing tools."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
from collections.abc import Awaitable, Callable
|
|
5
|
+
from dataclasses import dataclass
|
|
4
6
|
|
|
5
7
|
from henchman.providers.base import ToolDeclaration
|
|
6
|
-
from henchman.tools.base import ConfirmationRequest, Tool, ToolResult
|
|
8
|
+
from henchman.tools.base import ConfirmationRequest, Tool, ToolKind, ToolResult
|
|
9
|
+
from henchman.utils.retry import RetryConfig, retry_async
|
|
7
10
|
|
|
8
11
|
# Type alias for confirmation handler
|
|
9
12
|
ConfirmationHandler = Callable[[ConfirmationRequest], Awaitable[bool]]
|
|
@@ -12,6 +15,21 @@ ConfirmationHandler = Callable[[ConfirmationRequest], Awaitable[bool]]
|
|
|
12
15
|
MAX_TOOL_OUTPUT = 50000
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
@dataclass
|
|
19
|
+
class BatchResult:
|
|
20
|
+
"""Result of a batch tool execution.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
results: Mapping of tool names to their results.
|
|
24
|
+
successful: Number of successful executions.
|
|
25
|
+
failed: Number of failed executions.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
results: dict[str, ToolResult]
|
|
29
|
+
successful: int
|
|
30
|
+
failed: int
|
|
31
|
+
|
|
32
|
+
|
|
15
33
|
class ToolRegistry:
|
|
16
34
|
"""Registry for managing tools and their execution.
|
|
17
35
|
|
|
@@ -26,12 +44,29 @@ class ToolRegistry:
|
|
|
26
44
|
>>> result = await registry.execute("read_file", {"path": "/etc/hosts"})
|
|
27
45
|
"""
|
|
28
46
|
|
|
29
|
-
def __init__(
|
|
30
|
-
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
retry_config: RetryConfig | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Initialize an empty tool registry.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
retry_config: Optional retry configuration for network tools.
|
|
55
|
+
If None, uses defaults (3 retries, 1s base delay).
|
|
56
|
+
"""
|
|
31
57
|
self._tools: dict[str, Tool] = {}
|
|
32
58
|
self._confirmation_handler: ConfirmationHandler | None = None
|
|
33
59
|
self._auto_approve_policies: set[str] = set()
|
|
34
60
|
self._plan_mode: bool = False
|
|
61
|
+
self._retry_config = retry_config or RetryConfig()
|
|
62
|
+
|
|
63
|
+
def set_retry_config(self, config: RetryConfig) -> None:
|
|
64
|
+
"""Set the retry configuration for network tools.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
config: Retry configuration.
|
|
68
|
+
"""
|
|
69
|
+
self._retry_config = config
|
|
35
70
|
|
|
36
71
|
def set_plan_mode(self, enabled: bool) -> None:
|
|
37
72
|
"""Enable or disable Plan Mode (Read-Only).
|
|
@@ -143,7 +178,7 @@ class ToolRegistry:
|
|
|
143
178
|
2. Check Plan Mode restrictions
|
|
144
179
|
3. Check if confirmation is needed (unless auto-approved or READ tool)
|
|
145
180
|
4. Call confirmation handler if needed
|
|
146
|
-
5. Execute the tool if approved
|
|
181
|
+
5. Execute the tool if approved (with retries for NETWORK tools)
|
|
147
182
|
|
|
148
183
|
Args:
|
|
149
184
|
name: The name of the tool to execute.
|
|
@@ -161,9 +196,8 @@ class ToolRegistry:
|
|
|
161
196
|
)
|
|
162
197
|
|
|
163
198
|
# Check Plan Mode
|
|
164
|
-
from henchman.tools.base import ToolKind
|
|
165
199
|
if self._plan_mode and tool.kind in (ToolKind.WRITE, ToolKind.EXECUTE, ToolKind.NETWORK):
|
|
166
|
-
|
|
200
|
+
return ToolResult(
|
|
167
201
|
content=f"Tool '{name}' is disabled in Plan Mode. Use /plan to toggle.",
|
|
168
202
|
success=False,
|
|
169
203
|
error="Tool disabled in Plan Mode",
|
|
@@ -181,8 +215,21 @@ class ToolRegistry:
|
|
|
181
215
|
error="Execution denied by user",
|
|
182
216
|
)
|
|
183
217
|
|
|
184
|
-
# Execute the tool
|
|
185
|
-
|
|
218
|
+
# Execute the tool (with retries for NETWORK tools)
|
|
219
|
+
try:
|
|
220
|
+
if tool.kind == ToolKind.NETWORK and self._retry_config.max_retries > 0:
|
|
221
|
+
result = await retry_async(
|
|
222
|
+
lambda: tool.execute(**params),
|
|
223
|
+
config=self._retry_config,
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
result = await tool.execute(**params)
|
|
227
|
+
except Exception as exc:
|
|
228
|
+
return ToolResult(
|
|
229
|
+
content=f"Error executing tool '{name}': {exc}",
|
|
230
|
+
success=False,
|
|
231
|
+
error=str(exc),
|
|
232
|
+
)
|
|
186
233
|
|
|
187
234
|
# Truncate large outputs to prevent context overflow
|
|
188
235
|
if result.content and len(result.content) > MAX_TOOL_OUTPUT:
|
|
@@ -196,3 +243,59 @@ class ToolRegistry:
|
|
|
196
243
|
)
|
|
197
244
|
|
|
198
245
|
return result
|
|
246
|
+
|
|
247
|
+
async def execute_batch(
|
|
248
|
+
self,
|
|
249
|
+
calls: list[tuple[str, dict[str, object]]],
|
|
250
|
+
) -> BatchResult:
|
|
251
|
+
"""Execute multiple tools in parallel.
|
|
252
|
+
|
|
253
|
+
Independent tool calls are executed concurrently using asyncio.gather().
|
|
254
|
+
This is more efficient than sequential execution for I/O-bound operations.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
calls: List of (tool_name, params) tuples to execute.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
BatchResult containing individual results and success/failure counts.
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
>>> results = await registry.execute_batch([
|
|
264
|
+
... ("read_file", {"path": "/etc/hosts"}),
|
|
265
|
+
... ("read_file", {"path": "/etc/passwd"}),
|
|
266
|
+
... ("glob", {"pattern": "*.py"}),
|
|
267
|
+
... ])
|
|
268
|
+
>>> print(f"Completed: {results.successful} succeeded, {results.failed} failed")
|
|
269
|
+
"""
|
|
270
|
+
if not calls:
|
|
271
|
+
return BatchResult(results={}, successful=0, failed=0)
|
|
272
|
+
|
|
273
|
+
# Execute all tools concurrently
|
|
274
|
+
tasks = [self.execute(name, params) for name, params in calls]
|
|
275
|
+
results_list = await asyncio.gather(*tasks, return_exceptions=True)
|
|
276
|
+
|
|
277
|
+
# Collect results
|
|
278
|
+
results: dict[str, ToolResult] = {}
|
|
279
|
+
successful = 0
|
|
280
|
+
failed = 0
|
|
281
|
+
|
|
282
|
+
for i, result in enumerate(results_list):
|
|
283
|
+
tool_name = calls[i][0]
|
|
284
|
+
key = f"{tool_name}_{i}" # Unique key for duplicate tool names
|
|
285
|
+
|
|
286
|
+
if isinstance(result, BaseException):
|
|
287
|
+
results[key] = ToolResult(
|
|
288
|
+
content=f"Error: {result}",
|
|
289
|
+
success=False,
|
|
290
|
+
error=str(result),
|
|
291
|
+
)
|
|
292
|
+
failed += 1
|
|
293
|
+
else:
|
|
294
|
+
tool_result: ToolResult = result
|
|
295
|
+
results[key] = tool_result
|
|
296
|
+
if tool_result.success:
|
|
297
|
+
successful += 1
|
|
298
|
+
else:
|
|
299
|
+
failed += 1
|
|
300
|
+
|
|
301
|
+
return BatchResult(results=results, successful=successful, failed=failed)
|
henchman/utils/__init__.py
CHANGED
henchman/utils/retry.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Retry utilities with exponential backoff for resilient operations.
|
|
2
|
+
|
|
3
|
+
This module provides an async retry decorator with configurable exponential
|
|
4
|
+
backoff for handling transient failures in network operations and other
|
|
5
|
+
potentially flaky operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import functools
|
|
12
|
+
import random
|
|
13
|
+
from collections.abc import Awaitable, Callable
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import ParamSpec, TypeVar
|
|
16
|
+
|
|
17
|
+
P = ParamSpec("P")
|
|
18
|
+
R = TypeVar("R")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class RetryConfig:
|
|
23
|
+
"""Configuration for retry behavior.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
max_retries: Maximum number of retry attempts (0 = no retries).
|
|
27
|
+
base_delay: Base delay in seconds between retries.
|
|
28
|
+
max_delay: Maximum delay in seconds (cap for exponential growth).
|
|
29
|
+
exponential_base: Base for exponential backoff (default 2.0).
|
|
30
|
+
jitter: Whether to add random jitter to delay (prevents thundering herd).
|
|
31
|
+
retryable_exceptions: Tuple of exception types to retry on.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
max_retries: int = 3
|
|
35
|
+
base_delay: float = 1.0
|
|
36
|
+
max_delay: float = 30.0
|
|
37
|
+
exponential_base: float = 2.0
|
|
38
|
+
jitter: bool = True
|
|
39
|
+
retryable_exceptions: tuple[type[Exception], ...] = (
|
|
40
|
+
ConnectionError,
|
|
41
|
+
TimeoutError,
|
|
42
|
+
OSError,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def calculate_delay(attempt: int, config: RetryConfig) -> float:
|
|
47
|
+
"""Calculate delay for a retry attempt with exponential backoff.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
attempt: The current retry attempt number (0-indexed).
|
|
51
|
+
config: Retry configuration.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Delay in seconds before the next retry.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> cfg = RetryConfig(base_delay=1.0, exponential_base=2.0, jitter=False)
|
|
58
|
+
>>> calculate_delay(0, cfg)
|
|
59
|
+
1.0
|
|
60
|
+
>>> calculate_delay(1, cfg)
|
|
61
|
+
2.0
|
|
62
|
+
>>> calculate_delay(2, cfg)
|
|
63
|
+
4.0
|
|
64
|
+
"""
|
|
65
|
+
delay = config.base_delay * (config.exponential_base**attempt)
|
|
66
|
+
delay = min(delay, config.max_delay)
|
|
67
|
+
|
|
68
|
+
if config.jitter:
|
|
69
|
+
# Add random jitter between 0-50% of the delay
|
|
70
|
+
jitter_amount = delay * random.uniform(0, 0.5) # noqa: S311
|
|
71
|
+
delay += jitter_amount
|
|
72
|
+
|
|
73
|
+
return delay
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def with_retry(
|
|
77
|
+
config: RetryConfig | None = None,
|
|
78
|
+
) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[R]]]:
|
|
79
|
+
"""Decorator for adding retry logic with exponential backoff.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
config: Retry configuration. If None, uses defaults.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Decorated async function with retry logic.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> @with_retry(RetryConfig(max_retries=3))
|
|
89
|
+
... async def fetch_data(url: str) -> str:
|
|
90
|
+
... # Network operation that might fail
|
|
91
|
+
... return await http_get(url)
|
|
92
|
+
"""
|
|
93
|
+
if config is None:
|
|
94
|
+
config = RetryConfig()
|
|
95
|
+
|
|
96
|
+
def decorator(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
|
|
97
|
+
"""Inner decorator that wraps the function with retry logic."""
|
|
98
|
+
@functools.wraps(func)
|
|
99
|
+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
100
|
+
"""Wrapper function that implements retry with exponential backoff."""
|
|
101
|
+
last_exception: Exception | None = None
|
|
102
|
+
|
|
103
|
+
for attempt in range(config.max_retries + 1):
|
|
104
|
+
try:
|
|
105
|
+
return await func(*args, **kwargs)
|
|
106
|
+
except config.retryable_exceptions as exc:
|
|
107
|
+
last_exception = exc
|
|
108
|
+
|
|
109
|
+
if attempt < config.max_retries:
|
|
110
|
+
delay = calculate_delay(attempt, config)
|
|
111
|
+
await asyncio.sleep(delay)
|
|
112
|
+
# On last attempt, fall through to raise
|
|
113
|
+
|
|
114
|
+
# Should never reach here without an exception, but satisfy type checker
|
|
115
|
+
if last_exception is not None:
|
|
116
|
+
raise last_exception
|
|
117
|
+
msg = "Retry logic error: no exception captured"
|
|
118
|
+
raise RuntimeError(msg) # pragma: no cover
|
|
119
|
+
|
|
120
|
+
return wrapper
|
|
121
|
+
|
|
122
|
+
return decorator
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def retry_async(
|
|
126
|
+
func: Callable[[], Awaitable[R]],
|
|
127
|
+
config: RetryConfig | None = None,
|
|
128
|
+
) -> R:
|
|
129
|
+
"""Execute an async function with retry logic.
|
|
130
|
+
|
|
131
|
+
This is a functional alternative to the decorator for cases where
|
|
132
|
+
you want to retry a specific call rather than decorating a function.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
func: Zero-argument async callable to execute.
|
|
136
|
+
config: Retry configuration. If None, uses defaults.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Result of the function call.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
Exception: The last exception if all retries fail.
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
>>> result = await retry_async(
|
|
146
|
+
... lambda: fetch_data("https://api.example.com"),
|
|
147
|
+
... config=RetryConfig(max_retries=3)
|
|
148
|
+
... )
|
|
149
|
+
"""
|
|
150
|
+
if config is None:
|
|
151
|
+
config = RetryConfig()
|
|
152
|
+
|
|
153
|
+
@with_retry(config)
|
|
154
|
+
async def wrapped() -> R:
|
|
155
|
+
"""Wrapped function with retry logic applied."""
|
|
156
|
+
return await func()
|
|
157
|
+
|
|
158
|
+
return await wrapped()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
__all__ = [
|
|
162
|
+
"RetryConfig",
|
|
163
|
+
"calculate_delay",
|
|
164
|
+
"retry_async",
|
|
165
|
+
"with_retry",
|
|
166
|
+
]
|
henchman/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: henchman-ai
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: A model-agnostic AI agent CLI - your AI henchman for the terminal
|
|
5
5
|
Project-URL: Homepage, https://github.com/MGPowerlytics/henchman-ai
|
|
6
6
|
Project-URL: Repository, https://github.com/MGPowerlytics/henchman-ai
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
henchman/__init__.py,sha256=P_jCbtgAVbk2hn6uMum2UYkE7ptT361mWRkUZz0xKvk,148
|
|
2
2
|
henchman/__main__.py,sha256=3oRWZvoWON5ErlJFYOOSU5p1PERRyK6MkT2LGEnbb2o,131
|
|
3
|
-
henchman/version.py,sha256=
|
|
3
|
+
henchman/version.py,sha256=FD_uykl80efNIvznILv6PLP9U62_N9a6PJtpNG-nhlA,160
|
|
4
4
|
henchman/cli/__init__.py,sha256=Gv86a_heuBLqUd-y46JZUyzUaDl5H-9RtcWGr3rMwBw,673
|
|
5
5
|
henchman/cli/app.py,sha256=AFiMOfqYdwJrzcp5LRqwgwic2A6yhAUr_01w6BQwPq8,6097
|
|
6
6
|
henchman/cli/console.py,sha256=TOuGBSNUaxxQypmmzC0P1IY7tBNlaTgAZesKy8uuZN4,7850
|
|
7
|
-
henchman/cli/input.py,sha256=
|
|
7
|
+
henchman/cli/input.py,sha256=0qW36f7f06ct4XXca7ooxkTShID-QXkLtmROh_xso04,4632
|
|
8
8
|
henchman/cli/json_output.py,sha256=9kP9S5q0xBgP4HQGTT4P6DDT76F9VVTdEY_KiEpoZnI,2669
|
|
9
9
|
henchman/cli/prompts.py,sha256=AxUN-JfWSetOgIwhVxgouQetNqY8hTc7FnLO5jb00LI,5402
|
|
10
10
|
henchman/cli/repl.py,sha256=7nO2Lfy5pUoZiSmWH1ne755_g2SRldMB9CIgz1GZXDg,19023
|
|
@@ -20,7 +20,7 @@ henchman/cli/commands/skill.py,sha256=azXb6-KXjtZKwHiBV-Ppk6CdJQKZhetr46hNgZ_r45
|
|
|
20
20
|
henchman/cli/commands/unlimited.py,sha256=eFMTwrcUFWbfJnXpwBcRqviYt66tDz4xAYBDcton50Y,2101
|
|
21
21
|
henchman/config/__init__.py,sha256=Q3eooEkht80FPzLJQgc-JNFfnFXS6uJlXyQyGl58QYk,557
|
|
22
22
|
henchman/config/context.py,sha256=bJSV56bQFAgECNMDBgM2ZwIkn0h50uibUtUWaQnMq_0,5007
|
|
23
|
-
henchman/config/schema.py,sha256=
|
|
23
|
+
henchman/config/schema.py,sha256=Ur-GP9aQv5WVAr9k6ordgK1iHkl3L9UWVDxOEYYe-Zw,4340
|
|
24
24
|
henchman/config/settings.py,sha256=KwlWlX3Ogqb_ByIchX9i_p8VM16umDNOa0n-Q4SrxTs,4501
|
|
25
25
|
henchman/core/__init__.py,sha256=BtOHfsJB6eW-QhtK4sbqOzzO6GLqWeVLkewOA-wuqL0,521
|
|
26
26
|
henchman/core/agent.py,sha256=l9BJO8Zw4bMdUyTDjcZKG84WdZ1Kndm3Y09oUAZFYp0,13475
|
|
@@ -51,7 +51,7 @@ henchman/skills/models.py,sha256=Vvr6ObxQe5G8EwuLW2Odzpej3CfyqGkgXgCQcY91wps,833
|
|
|
51
51
|
henchman/skills/store.py,sha256=z_qHnVdyHAm-jtGNy5b33d9jQ-3pN-mkjrBAzc7CA5U,5331
|
|
52
52
|
henchman/tools/__init__.py,sha256=NLQuYRtPECju8TZHg-ZgVUrtdlHdMTu7P8C3D4Nhdqg,440
|
|
53
53
|
henchman/tools/base.py,sha256=PPeKS6jOCv3jFSgjd5j3kzgjTzw7VpBAY3ikmeDlIcI,4820
|
|
54
|
-
henchman/tools/registry.py,sha256=
|
|
54
|
+
henchman/tools/registry.py,sha256=YrDz29mwMGPGdJB5RD0QlakHiJ-24p3d_jnIaMY05hI,10228
|
|
55
55
|
henchman/tools/builtins/__init__.py,sha256=EtrnR1rtFh0uBqDembg236HnZfQXSLUHJachESvYG28,715
|
|
56
56
|
henchman/tools/builtins/ask_user.py,sha256=xPu74cB0rYahZHajVdjKgdmKU121SWyAgZSkU_wBYjQ,3007
|
|
57
57
|
henchman/tools/builtins/file_edit.py,sha256=VjfpYVZulpIBufRSIsTx9eD5gYGnSybksyo5vGCL4wo,3709
|
|
@@ -62,12 +62,13 @@ henchman/tools/builtins/grep.py,sha256=PV8X2ydnAutrWCS5VR9lABFpfSv0Olzsqa1Ktb5X4
|
|
|
62
62
|
henchman/tools/builtins/ls.py,sha256=5iSqHilrEiZ8ziOG4nKwC90fuLEx01V_0BzfS2PNAro,4167
|
|
63
63
|
henchman/tools/builtins/shell.py,sha256=Gx8x1jBq1NvERFnc-kUNMovFoWg_i4IrV_askSECfEM,4134
|
|
64
64
|
henchman/tools/builtins/web_fetch.py,sha256=uwgZm0ye3yDuS2U2DPV4D-8bjviYDTKN-cNi7mCMRpw,3370
|
|
65
|
-
henchman/utils/__init__.py,sha256=
|
|
65
|
+
henchman/utils/__init__.py,sha256=ayu2XRNx3Fw0z8vbIne63A3gBjxu779QE8sUQsjNnm4,240
|
|
66
66
|
henchman/utils/compaction.py,sha256=jPpJ5tQm-IBn4YChiGrKy8u_K4OJ23lk3Jvq8sNbQYc,22763
|
|
67
|
+
henchman/utils/retry.py,sha256=sobZk9LLGxglSJw_jeNaBYCrvH14YNFrBVyp_OwLWcw,4993
|
|
67
68
|
henchman/utils/tokens.py,sha256=D9H4ciFNH7l1b05IGbw0U0tmy2yF5aItFZyDufGF53k,5665
|
|
68
69
|
henchman/utils/validation.py,sha256=moj4LQXVXt2J-3_pWVH_0-EabyRYApOU2Oh5JSTIua8,4146
|
|
69
|
-
henchman_ai-0.1.
|
|
70
|
-
henchman_ai-0.1.
|
|
71
|
-
henchman_ai-0.1.
|
|
72
|
-
henchman_ai-0.1.
|
|
73
|
-
henchman_ai-0.1.
|
|
70
|
+
henchman_ai-0.1.9.dist-info/METADATA,sha256=C1bCnWJpK3B-vjpJ0deP2OWikysCGW9hOkBkbfAB9pQ,3492
|
|
71
|
+
henchman_ai-0.1.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
72
|
+
henchman_ai-0.1.9.dist-info/entry_points.txt,sha256=dtPyd6BzK3A8lmrj1KXTFlHBplIWcWMdryjtR0jw5iU,51
|
|
73
|
+
henchman_ai-0.1.9.dist-info/licenses/LICENSE,sha256=TMoSCCG1I1vCMK-Bjtvxe80E8kIdSdrtuQXYHc_ahqg,1064
|
|
74
|
+
henchman_ai-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|