Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion openhack/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,16 @@ async def _agent_loop(self) -> dict:
# (for example, two identical greetings). Caching it across
# turns rejects a valid completion and makes the continuation
# guard ask the model for a second response.
if not is_finish_task and key in self._call_cache:
if tool_call.parse_error:
result = {
"error": "tool_call_truncated",
"message": (
f"Arguments for '{tool_call.name}' were truncated due to token limits "
f"(parse error: {tool_call.parse_error}). "
"Please retry and call the tool with more concise arguments."
),
}
elif not is_finish_task and key in self._call_cache:
# Anti-repetition: this exact call already ran — don't
# re-execute (saves tokens) and nudge the agent to move on.
prev_turn, prev_summ = self._call_cache[key]
Expand Down
11 changes: 10 additions & 1 deletion openhack/agents/browser_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,16 @@ async def run(self, task: str, context: Optional[dict] = None) -> dict:
)

try:
if tool_call.name == "browser_navigate":
if tool_call.parse_error:
result = {
"error": "tool_call_truncated",
"message": (
f"Arguments for '{tool_call.name}' were truncated due to token limits "
f"(parse error: {tool_call.parse_error}). "
"Please retry and call the tool with more concise arguments."
),
}
elif tool_call.name == "browser_navigate":
result = await self._handle_browser_navigate(tool_call.arguments)
elif tool_call.name == "browser_click":
result = await self._handle_browser_click(tool_call.arguments)
Expand Down
47 changes: 42 additions & 5 deletions openhack/agents/hunter.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,29 @@ def _correct_line_number(self, file_path: str, line_number: int, code_snippet: s
return line_number

def _handle_report_finding(self, args: dict) -> dict:
file_path = args.get("file_path", "")
raw_file_path = args.get("file_path")
raw_desc = args.get("description")

if not isinstance(raw_file_path, str) or not isinstance(raw_desc, str):
logger.warning(f"Finding rejected: missing or non-string file_path/description (args={args})")
return {
"status": "rejected",
"reason": "Missing or invalid required fields: 'file_path' and 'description' must be non-empty strings.",
}

file_path = raw_file_path.strip()
description = raw_desc.strip()

# Reject findings missing core required fields
if not file_path or not description:
logger.warning(f"Finding rejected: missing file_path or description (args={args})")
return {
"status": "rejected",
"reason": "Missing required fields: 'file_path' and 'description' must not be empty.",
}

# Pre-filter: reject findings in excluded paths
if file_path and self._is_excluded_path(file_path):
if self._is_excluded_path(file_path):
logger.info(f"Finding rejected (excluded path): {args.get('category', '?')} in {file_path}")
return {"status": "rejected", "reason": f"File is in an excluded path (test/CLI/docs/examples): {file_path}"}

Expand Down Expand Up @@ -402,13 +421,31 @@ async def run(self, task: str, context: Optional[dict] = None) -> dict:
},
)

if tool_call.name == "report_finding":
result = self._handle_report_finding(tool_call.arguments)
if tool_call.parse_error:
result = {
"error": "tool_call_truncated",
"message": (
f"Arguments for '{tool_call.name}' were truncated due to token limits "
f"(parse error: {tool_call.parse_error}). "
"Please retry and call the tool with more concise arguments, "
"or report findings one at a time."
),
}
elif tool_call.name == "report_finding":
try:
result = self._handle_report_finding(tool_call.arguments)
except Exception as e:
logger.warning(f"[{self.name}] Failed to handle report_finding: {e}")
result = {"error": f"Failed to record finding: {e}"}
iterations_since_finding = 0
consecutive_reads = 0
analysis_mode = False
elif tool_call.name == "finish_hunt":
result = self._handle_finish_hunt(tool_call.arguments)
try:
result = self._handle_finish_hunt(tool_call.arguments)
except Exception as e:
logger.warning(f"[{self.name}] Failed to handle finish_hunt: {e}")
result = {"error": f"Failed to finish hunt: {e}"}
should_finish = True
else:
try:
Expand Down
11 changes: 10 additions & 1 deletion openhack/agents/sandbox_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,16 @@ async def run(self, task: str, context: Optional[dict] = None) -> dict:
},
)

if tool_call.name == "sandbox_http_request":
if tool_call.parse_error:
result = {
"error": "tool_call_truncated",
"message": (
f"Arguments for '{tool_call.name}' were truncated due to token limits "
f"(parse error: {tool_call.parse_error}). "
"Please retry and call the tool with more concise arguments."
),
}
elif tool_call.name == "sandbox_http_request":
result = await self._handle_sandbox_http_request(tool_call.arguments)
elif tool_call.name == "sandbox_multi_step":
result = await self._handle_sandbox_multi_step(tool_call.arguments)
Expand Down
28 changes: 24 additions & 4 deletions openhack/agents/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ def get_tools(self) -> list[dict]:
return super().get_tools() + [VALIDATE_FINDING_TOOL, FINISH_VALIDATION_TOOL]

def _handle_validate_finding(self, args: dict) -> dict:
status = args.get("status", "").lower()
raw_status = args.get("status")
if not isinstance(raw_status, str) or not raw_status.strip():
return {"error": "Missing or invalid required field: 'status' must be a non-empty string."}
status = raw_status.lower().strip()
if self.original_finding_index is not None:
original_index = self.original_finding_index
else:
Expand Down Expand Up @@ -189,10 +192,27 @@ async def run(self, task: str, context: Optional[dict] = None) -> dict:
},
)

if tool_call.name == "validate_finding":
result = self._handle_validate_finding(tool_call.arguments)
if tool_call.parse_error:
result = {
"error": "tool_call_truncated",
"message": (
f"Arguments for '{tool_call.name}' were truncated due to token limits "
f"(parse error: {tool_call.parse_error}). "
"Please retry and call the tool with more concise arguments."
),
}
elif tool_call.name == "validate_finding":
try:
result = self._handle_validate_finding(tool_call.arguments)
except Exception as e:
logger.warning(f"[{self.name}] Failed to handle validate_finding: {e}")
result = {"error": f"Failed to validate finding: {e}"}
elif tool_call.name == "finish_validation":
result = self._handle_finish_validation(tool_call.arguments)
try:
result = self._handle_finish_validation(tool_call.arguments)
except Exception as e:
logger.warning(f"[{self.name}] Failed to handle finish_validation: {e}")
result = {"error": f"Failed to finish validation: {e}"}
should_finish = True
else:
result = self.tools.execute_tool(tool_call.name, tool_call.arguments)
Expand Down
Loading