@miller-tech/uap 1.148.6 → 1.148.8
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/policy-select.js +1 -1
- package/dist/cli/policy.js +2 -2
- package/dist/delivery/user-validation.d.ts +8 -0
- package/dist/delivery/user-validation.d.ts.map +1 -1
- package/dist/delivery/user-validation.js +22 -1
- package/dist/delivery/user-validation.js.map +1 -1
- package/dist/delivery/vision-judge.d.ts.map +1 -1
- package/dist/delivery/vision-judge.js +40 -15
- package/dist/delivery/vision-judge.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/schemas/policies/adr-guard.md +1 -1
- package/src/policies/schemas/policies/coord-overlap.md +1 -1
- package/src/policies/schemas/policies/delivery-enforcement.md +1 -1
- package/src/policies/schemas/policies/enforcement-self-protect.md +2 -0
- package/src/policies/schemas/policies/local-build-before-push.md +1 -1
- package/src/policies/schemas/policies/mcp-router-first.md +1 -1
- package/src/policies/schemas/policies/parallel-reads.md +1 -1
- package/src/policies/schemas/policies/pay2u-architecture-rules.md +1 -1
- package/src/policies/schemas/policies/pay2u-enforcement-hooks.md +1 -1
- package/src/policies/schemas/policies/pay2u-quick-reference.md +1 -1
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +68 -0
- package/tools/agents/tests/test_empty_tool_loop_break.py +69 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Empty-tool-loop breaker: after N consecutive fully-empty responses under a
|
|
3
|
+
forced tool_choice, release the force + inject a plain-text directive so a stuck
|
|
4
|
+
model can report the blocker (e.g. a deleted deliverable) instead of spinning."""
|
|
5
|
+
import importlib.util
|
|
6
|
+
import os
|
|
7
|
+
import unittest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _load_proxy():
|
|
12
|
+
os.environ.setdefault("PROXY_EMPTY_TOOL_LOOP_BREAK", "3")
|
|
13
|
+
p = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
14
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", p)
|
|
15
|
+
m = importlib.util.module_from_spec(spec)
|
|
16
|
+
spec.loader.exec_module(m)
|
|
17
|
+
return m
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
proxy = _load_proxy()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def forced_body():
|
|
24
|
+
return {"tool_choice": "required", "messages": [{"role": "user", "content": "go"}]}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EmptyToolLoopBreakTests(unittest.TestCase):
|
|
28
|
+
def setUp(self):
|
|
29
|
+
self.m = proxy.SessionMonitor()
|
|
30
|
+
|
|
31
|
+
def test_no_op_below_threshold(self):
|
|
32
|
+
body = forced_body()
|
|
33
|
+
self.m.consecutive_empty_tool_turns = proxy.PROXY_EMPTY_TOOL_LOOP_BREAK - 1
|
|
34
|
+
proxy._maybe_break_empty_tool_loop(body, self.m)
|
|
35
|
+
self.assertEqual(body["tool_choice"], "required") # force intact
|
|
36
|
+
self.assertEqual(len(body["messages"]), 1) # no directive injected
|
|
37
|
+
|
|
38
|
+
def test_releases_and_directs_at_threshold(self):
|
|
39
|
+
body = forced_body()
|
|
40
|
+
self.m.consecutive_empty_tool_turns = proxy.PROXY_EMPTY_TOOL_LOOP_BREAK
|
|
41
|
+
proxy._maybe_break_empty_tool_loop(body, self.m)
|
|
42
|
+
self.assertEqual(body["tool_choice"], "auto") # force released → prose possible
|
|
43
|
+
self.assertEqual(len(body["messages"]), 2) # plain-text directive appended
|
|
44
|
+
self.assertIn("PLAIN TEXT", body["messages"][-1]["content"])
|
|
45
|
+
self.assertEqual(self.m.consecutive_empty_tool_turns, 0) # one-shot per window
|
|
46
|
+
|
|
47
|
+
def test_disabled_when_threshold_zero(self):
|
|
48
|
+
body = forced_body()
|
|
49
|
+
old = proxy.PROXY_EMPTY_TOOL_LOOP_BREAK
|
|
50
|
+
try:
|
|
51
|
+
proxy.PROXY_EMPTY_TOOL_LOOP_BREAK = 0
|
|
52
|
+
self.m.consecutive_empty_tool_turns = 99
|
|
53
|
+
proxy._maybe_break_empty_tool_loop(body, self.m)
|
|
54
|
+
self.assertEqual(body["tool_choice"], "required") # disabled → no-op
|
|
55
|
+
finally:
|
|
56
|
+
proxy.PROXY_EMPTY_TOOL_LOOP_BREAK = old
|
|
57
|
+
|
|
58
|
+
def test_leaves_unforced_tool_choice_alone_but_still_directs(self):
|
|
59
|
+
# If tool_choice isn't 'required' (already auto), don't clobber it, but
|
|
60
|
+
# still surface the directive so the model breaks the empty streak.
|
|
61
|
+
body = {"tool_choice": "auto", "messages": [{"role": "user", "content": "go"}]}
|
|
62
|
+
self.m.consecutive_empty_tool_turns = proxy.PROXY_EMPTY_TOOL_LOOP_BREAK
|
|
63
|
+
proxy._maybe_break_empty_tool_loop(body, self.m)
|
|
64
|
+
self.assertEqual(body["tool_choice"], "auto")
|
|
65
|
+
self.assertEqual(len(body["messages"]), 2)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
unittest.main()
|