@miller-tech/uap 1.172.1 → 1.172.7
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/config/llama-profiles/gemma4-26b-a4b-mtp.env +145 -0
- package/config/llama-profiles/qwen36-35b-a3b.env +69 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/worktree.d.ts +11 -0
- package/dist/cli/worktree.d.ts.map +1 -1
- package/dist/cli/worktree.js +30 -1
- package/dist/cli/worktree.js.map +1 -1
- package/dist/models/openai-compat-client.d.ts.map +1 -1
- package/dist/models/openai-compat-client.js +68 -1
- package/dist/models/openai-compat-client.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/enforcement_infra_protect.py +47 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +94 -1
- package/tools/agents/tests/test_mandate_beats_recon.py +188 -0
- package/tools/agents/tests/test_mandate_deliver.py +35 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Precedence: the deliver mandate outranks recon convergence.
|
|
3
|
+
|
|
4
|
+
Both guards run on the same turn (mandate first) and both rewrite
|
|
5
|
+
tools/tool_choice, so without an explicit rule the later one -- recon -- wins
|
|
6
|
+
and silently releases the pin. That made the mandate's own docstring claim
|
|
7
|
+
("runs before the softer guards so the pin stands") untrue.
|
|
8
|
+
|
|
9
|
+
The regression is only observable now: until v1.172.2 the pin was encoded in a
|
|
10
|
+
form llama.cpp ignored, so recon had nothing to override.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import importlib.util
|
|
14
|
+
import os
|
|
15
|
+
import unittest
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _load_proxy(env: dict | None = None):
|
|
20
|
+
"""Import the proxy with `env` applied, then restore the environment.
|
|
21
|
+
|
|
22
|
+
Module-level constants (PROXY_*) are read at import, so a knob can only be
|
|
23
|
+
exercised by re-importing under a different environment.
|
|
24
|
+
"""
|
|
25
|
+
saved = {k: os.environ.get(k) for k in (env or {})}
|
|
26
|
+
try:
|
|
27
|
+
for k, v in (env or {}).items():
|
|
28
|
+
os.environ[k] = v
|
|
29
|
+
p = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
30
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy_mbr", p)
|
|
31
|
+
assert spec is not None and spec.loader is not None
|
|
32
|
+
m = importlib.util.module_from_spec(spec)
|
|
33
|
+
spec.loader.exec_module(m)
|
|
34
|
+
return m
|
|
35
|
+
finally:
|
|
36
|
+
for k, v in saved.items():
|
|
37
|
+
if v is None:
|
|
38
|
+
os.environ.pop(k, None)
|
|
39
|
+
else:
|
|
40
|
+
os.environ[k] = v
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
proxy = _load_proxy()
|
|
44
|
+
|
|
45
|
+
DELIVER_TOOL = {"type": "function", "function": {"name": "deliver", "parameters": {}}}
|
|
46
|
+
READ_TOOL = {"type": "function", "function": {"name": "read", "parameters": {}}}
|
|
47
|
+
GREP_TOOL = {"type": "function", "function": {"name": "grep", "parameters": {}}}
|
|
48
|
+
BLOCK_MSG = (
|
|
49
|
+
"BLOCKED: do not edit 'src/foo.ts' directly. To create or change code, call "
|
|
50
|
+
"the `deliver` tool (or run: uap deliver \"...\"). Do NOT retry this edit."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _blocked_body(tools):
|
|
55
|
+
return {
|
|
56
|
+
"tools": list(tools),
|
|
57
|
+
"messages": [
|
|
58
|
+
{"role": "assistant", "content": "editing",
|
|
59
|
+
"tool_calls": [{"function": {"name": "edit"}}]},
|
|
60
|
+
{"role": "tool", "content": BLOCK_MSG},
|
|
61
|
+
],
|
|
62
|
+
"tool_choice": "auto",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _deep_recon(monitor, mod):
|
|
67
|
+
"""Put the monitor well past the hard recon tier, so recon would definitely
|
|
68
|
+
fire and rewrite the request if it were allowed to run."""
|
|
69
|
+
monitor.consecutive_no_write_turns = int(
|
|
70
|
+
mod.PROXY_RECON_CONVERGENCE_THRESHOLD * mod.PROXY_RECON_HARD_MULTIPLIER
|
|
71
|
+
) + 10
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class MandateBeatsReconTest(unittest.TestCase):
|
|
75
|
+
def test_recon_cannot_release_a_pinned_turn(self):
|
|
76
|
+
mon = proxy.SessionMonitor()
|
|
77
|
+
mon.mandate_deliver_active = False
|
|
78
|
+
body = _blocked_body([READ_TOOL, GREP_TOOL, DELIVER_TOOL])
|
|
79
|
+
|
|
80
|
+
proxy._maybe_inject_mandate_deliver(body, mon)
|
|
81
|
+
self.assertTrue(mon.mandate_deliver_active)
|
|
82
|
+
self.assertEqual(body["tool_choice"], "required")
|
|
83
|
+
self.assertEqual([t["function"]["name"] for t in body["tools"]], ["deliver"])
|
|
84
|
+
|
|
85
|
+
_deep_recon(mon, proxy)
|
|
86
|
+
proxy._maybe_inject_recon_convergence(body, mon, [READ_TOOL, GREP_TOOL, DELIVER_TOOL])
|
|
87
|
+
|
|
88
|
+
# The pin must survive: still required, still exactly one tool.
|
|
89
|
+
self.assertEqual(body["tool_choice"], "required")
|
|
90
|
+
self.assertEqual([t["function"]["name"] for t in body["tools"]], ["deliver"])
|
|
91
|
+
|
|
92
|
+
def test_recon_still_runs_when_no_mandate_is_active(self):
|
|
93
|
+
"""The suppression must be scoped to mandated turns -- recon is the
|
|
94
|
+
read-forever deadlock breaker and must keep working otherwise."""
|
|
95
|
+
mon = proxy.SessionMonitor()
|
|
96
|
+
mon.mandate_deliver_active = False
|
|
97
|
+
body = {"tools": [READ_TOOL, GREP_TOOL], "messages": [{"role": "user", "content": "explore"}]}
|
|
98
|
+
_deep_recon(mon, proxy)
|
|
99
|
+
|
|
100
|
+
before = mon.recon_hard_fires
|
|
101
|
+
proxy._maybe_inject_recon_convergence(body, mon, [READ_TOOL, GREP_TOOL])
|
|
102
|
+
self.assertGreater(mon.recon_hard_fires, before)
|
|
103
|
+
|
|
104
|
+
def test_flag_is_cleared_on_a_TOOL_LESS_turn(self):
|
|
105
|
+
"""Regression: the reset used to live beside finalize_turn_active in
|
|
106
|
+
build_openai_request, which sits inside `if has_tools:` -- while both
|
|
107
|
+
_maybe_inject_mandate_deliver and _maybe_inject_recon_convergence are
|
|
108
|
+
called unconditionally. A tool-less turn therefore skipped the reset,
|
|
109
|
+
the mandate returned early (no deliver tool to find), and recon read a
|
|
110
|
+
stale True from an earlier turn and suppressed itself.
|
|
111
|
+
|
|
112
|
+
The mandate now owns the flag and clears it on every turn, so this
|
|
113
|
+
sequence -- mandate turn, then a turn with no tools at all -- must leave
|
|
114
|
+
it False.
|
|
115
|
+
"""
|
|
116
|
+
mon = proxy.SessionMonitor()
|
|
117
|
+
proxy._maybe_inject_mandate_deliver(_blocked_body([DELIVER_TOOL]), mon)
|
|
118
|
+
self.assertTrue(mon.mandate_deliver_active)
|
|
119
|
+
|
|
120
|
+
proxy._maybe_inject_mandate_deliver({"messages": [{"role": "user", "content": "hi"}]}, mon)
|
|
121
|
+
self.assertFalse(mon.mandate_deliver_active)
|
|
122
|
+
|
|
123
|
+
def test_flag_is_cleared_even_when_the_mandate_is_disabled(self):
|
|
124
|
+
"""The clear precedes the PROXY_MANDATE_DELIVER check, so turning the
|
|
125
|
+
mandate off cannot strand a True set while it was on."""
|
|
126
|
+
mod = _load_proxy({"PROXY_MANDATE_DELIVER": "off"})
|
|
127
|
+
self.assertFalse(mod.PROXY_MANDATE_DELIVER)
|
|
128
|
+
mon = mod.SessionMonitor()
|
|
129
|
+
mon.mandate_deliver_active = True # as if set on a previous turn
|
|
130
|
+
mod._maybe_inject_mandate_deliver(_blocked_body([DELIVER_TOOL]), mon)
|
|
131
|
+
self.assertFalse(mon.mandate_deliver_active)
|
|
132
|
+
|
|
133
|
+
def test_flag_is_per_turn_not_sticky(self):
|
|
134
|
+
"""A stale True would suppress recon forever after one mandate. The
|
|
135
|
+
request builder clears it each turn; assert the field is not latched by
|
|
136
|
+
the mandate itself."""
|
|
137
|
+
mon = proxy.SessionMonitor()
|
|
138
|
+
self.assertFalse(mon.mandate_deliver_active)
|
|
139
|
+
body = _blocked_body([DELIVER_TOOL])
|
|
140
|
+
proxy._maybe_inject_mandate_deliver(body, mon)
|
|
141
|
+
self.assertTrue(mon.mandate_deliver_active)
|
|
142
|
+
|
|
143
|
+
# Simulate the next turn's reset, then a turn with no block present.
|
|
144
|
+
mon.mandate_deliver_active = False
|
|
145
|
+
clean = {"tools": [DELIVER_TOOL], "messages": [{"role": "user", "content": "hello"}]}
|
|
146
|
+
proxy._maybe_inject_mandate_deliver(clean, mon)
|
|
147
|
+
self.assertFalse(mon.mandate_deliver_active)
|
|
148
|
+
|
|
149
|
+
def test_no_mandate_claimed_when_pin_is_impossible(self):
|
|
150
|
+
"""If deliver is absent the pin cannot be applied. Recording a mandate
|
|
151
|
+
anyway would suppress recon while leaving the model free to hand-edit --
|
|
152
|
+
strictly worse than letting recon run."""
|
|
153
|
+
mon = proxy.SessionMonitor()
|
|
154
|
+
body = _blocked_body([READ_TOOL])
|
|
155
|
+
proxy._maybe_inject_mandate_deliver(body, mon)
|
|
156
|
+
self.assertFalse(mon.mandate_deliver_active)
|
|
157
|
+
self.assertEqual(mon.mandate_deliver_fires, 0)
|
|
158
|
+
|
|
159
|
+
def test_precedence_off_reproduces_the_bug_it_fixes(self):
|
|
160
|
+
"""PROXY_MANDATE_BEATS_RECON=off restores the old ordering -- and pins
|
|
161
|
+
down exactly what that ordering costs.
|
|
162
|
+
|
|
163
|
+
This is the load-bearing assertion of the whole change: with the
|
|
164
|
+
precedence disabled, recon's hard tier sets tool_choice back to "auto",
|
|
165
|
+
releasing a mandate raised because a source edit had just been BLOCKED.
|
|
166
|
+
The model is then free to do what it was blocked for. If this test ever
|
|
167
|
+
stops reproducing that release, the precedence guard has become dead
|
|
168
|
+
code and the suppression above is no longer protecting anything.
|
|
169
|
+
"""
|
|
170
|
+
mod = _load_proxy({"PROXY_MANDATE_BEATS_RECON": "off"})
|
|
171
|
+
self.assertFalse(mod.PROXY_MANDATE_BEATS_RECON)
|
|
172
|
+
|
|
173
|
+
mon = mod.SessionMonitor()
|
|
174
|
+
body = _blocked_body([READ_TOOL, GREP_TOOL, DELIVER_TOOL])
|
|
175
|
+
mod._maybe_inject_mandate_deliver(body, mon)
|
|
176
|
+
self.assertTrue(mon.mandate_deliver_active)
|
|
177
|
+
self.assertEqual(body["tool_choice"], "required")
|
|
178
|
+
|
|
179
|
+
_deep_recon(mon, mod)
|
|
180
|
+
before = mon.recon_hard_fires
|
|
181
|
+
mod._maybe_inject_recon_convergence(body, mon, [READ_TOOL, GREP_TOOL, DELIVER_TOOL])
|
|
182
|
+
|
|
183
|
+
self.assertGreater(mon.recon_hard_fires, before) # recon ran
|
|
184
|
+
self.assertEqual(body["tool_choice"], "auto") # ...and released the pin
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
unittest.main()
|
|
@@ -46,7 +46,11 @@ class MandateDeliverTest(unittest.TestCase):
|
|
|
46
46
|
{"role": "tool", "content": BLOCK_MSG},
|
|
47
47
|
], tools=[DELIVER_TOOL])
|
|
48
48
|
proxy._maybe_inject_mandate_deliver(b, self.mon)
|
|
49
|
-
|
|
49
|
+
# "required" + a one-tool list, NOT the OpenAI object form: llama.cpp
|
|
50
|
+
# parses tool_choice as a string and silently falls back to "auto" when
|
|
51
|
+
# handed an object, which un-pinned every mandate.
|
|
52
|
+
self.assertEqual(b["tool_choice"], "required")
|
|
53
|
+
self.assertEqual([t["function"]["name"] for t in b["tools"]], ["deliver"])
|
|
50
54
|
self.assertEqual(self.mon.mandate_deliver_fires, 1)
|
|
51
55
|
self.assertTrue(any("MANDATORY" in (m.get("content") or "") for m in b["messages"]))
|
|
52
56
|
|
|
@@ -79,7 +83,36 @@ class MandateDeliverTest(unittest.TestCase):
|
|
|
79
83
|
mcp_deliver = {"type": "function", "function": {"name": "mcp__uap-router__deliver", "parameters": {}}}
|
|
80
84
|
b = _body([{"role": "tool", "content": BLOCK_MSG}], tools=[mcp_deliver])
|
|
81
85
|
proxy._maybe_inject_mandate_deliver(b, self.mon)
|
|
82
|
-
self.assertEqual(b["tool_choice"],
|
|
86
|
+
self.assertEqual(b["tool_choice"], "required")
|
|
87
|
+
self.assertEqual(
|
|
88
|
+
[t["function"]["name"] for t in b["tools"]], ["mcp__uap-router__deliver"]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def test_pin_narrows_tools_to_the_single_mandated_tool(self):
|
|
92
|
+
"""The pin must REMOVE the other tools. 'required' only means "some
|
|
93
|
+
tool", so leaving `read` advertised would let the model satisfy the
|
|
94
|
+
mandate by reading a file instead of delivering."""
|
|
95
|
+
b = _body([{"role": "tool", "content": BLOCK_MSG}], tools=[READ_TOOL, DELIVER_TOOL])
|
|
96
|
+
proxy._maybe_inject_mandate_deliver(b, self.mon)
|
|
97
|
+
self.assertEqual(b["tool_choice"], "required")
|
|
98
|
+
self.assertEqual([t["function"]["name"] for t in b["tools"]], ["deliver"])
|
|
99
|
+
|
|
100
|
+
def test_tool_choice_is_never_an_object(self):
|
|
101
|
+
"""Regression guard for the llama.cpp string-only parser. An object here
|
|
102
|
+
is not a hard error upstream -- it degrades to "auto" with only a log
|
|
103
|
+
line -- so nothing downstream would have caught the regression."""
|
|
104
|
+
b = _body([{"role": "tool", "content": BLOCK_MSG}], tools=[DELIVER_TOOL])
|
|
105
|
+
proxy._maybe_inject_mandate_deliver(b, self.mon)
|
|
106
|
+
self.assertIsInstance(b["tool_choice"], str)
|
|
107
|
+
self.assertIn(b["tool_choice"], {"auto", "none", "required"})
|
|
108
|
+
|
|
109
|
+
def test_pin_is_a_noop_when_the_named_tool_is_absent(self):
|
|
110
|
+
"""Forcing "required" while the mandated tool is missing would demand a
|
|
111
|
+
call the model cannot make. _pin_tool_choice_to must decline instead."""
|
|
112
|
+
b = _body([{"role": "user", "content": "hi"}], tools=[READ_TOOL], tool_choice="auto")
|
|
113
|
+
self.assertFalse(proxy._pin_tool_choice_to(b, "deliver"))
|
|
114
|
+
self.assertEqual(b["tool_choice"], "auto")
|
|
115
|
+
self.assertEqual([t["function"]["name"] for t in b["tools"]], ["read"])
|
|
83
116
|
|
|
84
117
|
def test_disabled_by_env(self):
|
|
85
118
|
old = os.environ.get("PROXY_MANDATE_DELIVER")
|