mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-08 23:33:16 +08:00
Block non-retryable tool re-calls and add congestion ops recipes.
After insufficient_scope/retry_forbidden, refuse the same tool for the rest of the turn (even with new args), emit pre-call WA progress from ToolExecutor, TTL-cache findTopologyPaths, and add bandwidth congestion short-intent + schedule template. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9bbac6c0b8
commit
54cb8af8b0
6 changed files with 201 additions and 3 deletions
|
|
@ -31,6 +31,11 @@ def test_license_short_intent() -> None:
|
|||
assert detect_ops_short_intent("@bot licence check") == "license"
|
||||
|
||||
|
||||
def test_congestion_short_intent() -> None:
|
||||
assert detect_ops_short_intent("bandwidth congestion top") == "congestion"
|
||||
assert detect_ops_short_intent("端口忙 拥塞") == "congestion"
|
||||
|
||||
|
||||
def test_identical_failed_retry_blocked_across_rounds(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "retry.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
|
|
@ -70,3 +75,56 @@ def test_identical_failed_retry_blocked_across_rounds(tmp_path: Path) -> None:
|
|||
assert calls["n"] == 1
|
||||
assert blocked.get("error_code") == "identical_retry_blocked"
|
||||
assert blocked.get("failure_class") == "retry_guard"
|
||||
|
||||
|
||||
def test_retry_forbidden_blocks_same_tool_different_args(tmp_path: Path) -> None:
|
||||
store = SqliteStore(str(tmp_path / "scope.sqlite"))
|
||||
sess = store.create_session("t")
|
||||
calls = {"n": 0}
|
||||
|
||||
def _handler(_args):
|
||||
calls["n"] += 1
|
||||
return {
|
||||
"ok": False,
|
||||
"error_code": "insufficient_scope",
|
||||
"error": "insufficient_scope:sql:query",
|
||||
"retry_forbidden": True,
|
||||
"failure_class": "auth",
|
||||
}
|
||||
|
||||
reg = ToolRegistry(
|
||||
[
|
||||
ToolSpec(
|
||||
name="mcp__netx__sqlQueryUme",
|
||||
description="sql",
|
||||
parameters={"type": "object", "properties": {"sql": {"type": "string"}}},
|
||||
handler=_handler,
|
||||
read_only=True,
|
||||
)
|
||||
]
|
||||
)
|
||||
ctx = ToolExecutionContext(
|
||||
store=store,
|
||||
tools=reg,
|
||||
session_id=sess.id,
|
||||
turn_uuid="turn-scope-1",
|
||||
lang="en",
|
||||
)
|
||||
ToolExecutor().execute_tool_uses(
|
||||
ctx=ctx,
|
||||
assistant_msg_id=1,
|
||||
tool_uses=[LLMToolCall(id="c1", name="mcp__netx__sqlQueryUme", arguments={"sql": "select 1"})],
|
||||
signature_budget=2,
|
||||
)
|
||||
assert calls["n"] == 1
|
||||
|
||||
_, results = ToolExecutor().execute_tool_uses(
|
||||
ctx=ctx,
|
||||
assistant_msg_id=2,
|
||||
tool_uses=[LLMToolCall(id="c2", name="mcp__netx__sqlQueryUme", arguments={"sql": "select 2"})],
|
||||
signature_budget=2,
|
||||
)
|
||||
blocked, _ = results["c2"]
|
||||
assert calls["n"] == 1
|
||||
assert blocked.get("error_code") == "retry_forbidden_blocked"
|
||||
assert blocked.get("retry_forbidden") is True
|
||||
|
|
|
|||
|
|
@ -75,10 +75,14 @@ class RecipeHelpersTests(unittest.TestCase):
|
|||
self.assertIn("ume_alarm_tally_daily", ids)
|
||||
self.assertIn("ume_critical_xlsx_daily", ids)
|
||||
self.assertIn("ne_license_check_weekly", ids)
|
||||
self.assertIn("bandwidth_congestion_daily", ids)
|
||||
tmpl = resolve_ops_recipe_template("alarm_tally")
|
||||
assert tmpl is not None
|
||||
self.assertTrue(recipe_has_playbook(tmpl))
|
||||
self.assertEqual((tmpl.get("source") or {}).get("template_id"), "ume_alarm_tally_daily")
|
||||
cong = resolve_ops_recipe_template("congestion")
|
||||
assert cong is not None
|
||||
self.assertEqual((cong.get("source") or {}).get("template_id"), "bandwidth_congestion_daily")
|
||||
self.assertIsNone(resolve_ops_recipe_template("nope"))
|
||||
|
||||
def test_turn_instruction_modes(self) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue