@miller-tech/uap 1.172.0 → 1.172.6

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.
Files changed (30) hide show
  1. package/config/llama-profiles/gemma4-26b-a4b-mtp.env +145 -0
  2. package/config/llama-profiles/qwen36-35b-a3b.env +69 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli/worktree.d.ts +11 -0
  5. package/dist/cli/worktree.d.ts.map +1 -1
  6. package/dist/cli/worktree.js +30 -1
  7. package/dist/cli/worktree.js.map +1 -1
  8. package/dist/delivery/execution-gate.d.ts +7 -0
  9. package/dist/delivery/execution-gate.d.ts.map +1 -1
  10. package/dist/delivery/execution-gate.js +42 -1
  11. package/dist/delivery/execution-gate.js.map +1 -1
  12. package/dist/delivery/interaction/runner.d.ts +10 -0
  13. package/dist/delivery/interaction/runner.d.ts.map +1 -1
  14. package/dist/delivery/interaction/runner.js +93 -11
  15. package/dist/delivery/interaction/runner.js.map +1 -1
  16. package/dist/delivery/interaction-gate.d.ts.map +1 -1
  17. package/dist/delivery/interaction-gate.js +7 -0
  18. package/dist/delivery/interaction-gate.js.map +1 -1
  19. package/dist/delivery/visual-gate.js.map +1 -1
  20. package/dist/models/openai-compat-client.d.ts.map +1 -1
  21. package/dist/models/openai-compat-client.js +68 -1
  22. package/dist/models/openai-compat-client.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  25. package/src/policies/enforcers/enforcement_self_protect.py +96 -5
  26. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  27. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  28. package/tools/agents/scripts/anthropic_proxy.py +94 -1
  29. package/tools/agents/tests/test_mandate_beats_recon.py +188 -0
  30. package/tools/agents/tests/test_mandate_deliver.py +35 -2
@@ -1468,6 +1468,7 @@ class SessionMonitor:
1468
1468
  doubling_break_fires: int = 0 # monotonic count of injected pivot directives
1469
1469
  last_doubling_obs: str = "" # msg-count:fingerprint key of the last counted observation
1470
1470
  mandate_deliver_fires: int = 0 # monotonic count of forced deliver-routings (mandate)
1471
+ mandate_deliver_active: bool = False # THIS turn is pinned to deliver (beats recon convergence)
1471
1472
  tool_starvation_streak: int = 0 # Consecutive forced turns with no tool_calls produced
1472
1473
  last_request_msg_count: int = 0 # Message count of the previous request (compaction-boundary detection)
1473
1474
  malformed_tool_streak: int = 0 # consecutive malformed pseudo tool payloads
@@ -5251,6 +5252,13 @@ def _maybe_inject_deferral_break(openai_body: dict, monitor: "SessionMonitor") -
5251
5252
  PROXY_MANDATE_DELIVER = os.environ.get("PROXY_MANDATE_DELIVER", "on").lower() not in {
5252
5253
  "0", "off", "false", "no",
5253
5254
  }
5255
+ # Precedence: when the mandate pins a turn to deliver, recon convergence must not
5256
+ # undo it. Both guards run on the same turn and both rewrite tools/tool_choice, so
5257
+ # without this the later one (recon) silently wins. Off => the old ordering, where
5258
+ # recon can release the pin mid-mandate.
5259
+ PROXY_MANDATE_BEATS_RECON = os.environ.get(
5260
+ "PROXY_MANDATE_BEATS_RECON", "on"
5261
+ ).lower() not in {"0", "off", "false", "no"}
5254
5262
  # Enforcer-block-SPECIFIC phrases only (src/policies/enforcers/delivery_enforcement.py).
5255
5263
  # Deliberately NOT matching the reactor's STANDING "route through deliver" guidance
5256
5264
  # (which is injected every turn) -- only the actual block event must trigger.
@@ -5318,11 +5326,65 @@ def _assistant_already_called_deliver(messages, deliver_name: str) -> bool:
5318
5326
  return False
5319
5327
 
5320
5328
 
5329
+ def _pin_tool_choice_to(openai_body: dict, tool_name: str) -> bool:
5330
+ """Constrain this turn to calling exactly `tool_name`. Returns True if pinned.
5331
+
5332
+ The obvious encoding -- OpenAI's `{"type": "function", "function": {"name":
5333
+ ...}}` -- does NOT work against llama.cpp. Its server reads the field as a
5334
+ STRING (`json_value(body, "tool_choice", std::string("auto"))`) and accepts
5335
+ only auto | none | required via common_chat_tool_choice_parse_oaicompat().
5336
+ Handed an object it logs
5337
+
5338
+ Wrong type supplied for parameter 'tool_choice'. Expected 'string',
5339
+ using default value
5340
+
5341
+ and falls back to "auto" -- so every "pin" silently became "the model may do
5342
+ whatever it likes". That is how a blocked source edit could be answered with
5343
+ "I will proceed by manually creating the files": the mandate was never a
5344
+ constraint, only the directive text asking nicely.
5345
+
5346
+ Express the same intent in the vocabulary llama.cpp actually has: demand SOME
5347
+ tool call, and narrow the advertised tools to just this one, so the only call
5348
+ that satisfies the demand is the intended one. "required" is equally valid
5349
+ OpenAI, so this stays correct against a non-llama.cpp upstream.
5350
+
5351
+ Narrowing is skipped if the tool is not present in `tools` -- sending
5352
+ "required" with the tool absent would force a call the model cannot make.
5353
+ """
5354
+ tools = openai_body.get("tools")
5355
+ if not isinstance(tools, list):
5356
+ return False
5357
+ wanted = str(tool_name).lower()
5358
+ pinned = [
5359
+ t for t in tools
5360
+ if isinstance(t, dict)
5361
+ and str((t.get("function") or {}).get("name") or "").lower() == wanted
5362
+ ]
5363
+ if not pinned:
5364
+ return False
5365
+ openai_body["tools"] = pinned
5366
+ openai_body["tool_choice"] = "required"
5367
+ return True
5368
+
5369
+
5321
5370
  def _maybe_inject_mandate_deliver(openai_body: dict, monitor: "SessionMonitor") -> None:
5322
5371
  """MANDATORY deliver-routing: when a direct source edit was just blocked by
5323
5372
  delivery-enforcement, force the next turn to call the `deliver` tool for ANY
5324
5373
  model. Pins tool_choice to the deliver tool + injects a terse directive.
5325
5374
  Runs before the softer guards so the pin stands. PROXY_MANDATE_DELIVER=off."""
5375
+ # Decide the per-turn flag HERE, unconditionally, before any early return.
5376
+ #
5377
+ # It previously lived beside finalize_turn_active in build_openai_request --
5378
+ # which sits inside `if has_tools:`, while this function and
5379
+ # _maybe_inject_recon_convergence are both called unconditionally. On a
5380
+ # TOOL-LESS turn the reset was therefore skipped, this function returned
5381
+ # early (no deliver tool to find), and recon read a stale True from an
5382
+ # earlier turn and suppressed itself. Owning the flag here makes it
5383
+ # impossible to leave stale: the one function that can set it True is the
5384
+ # same one that always clears it first, on every turn, whatever the caller
5385
+ # does. The clear precedes the PROXY_MANDATE_DELIVER check deliberately, so
5386
+ # disabling the mandate can never strand a True either.
5387
+ monitor.mandate_deliver_active = False
5326
5388
  if not PROXY_MANDATE_DELIVER:
5327
5389
  return
5328
5390
  deliver_name = _deliver_tool_name(openai_body.get("tools"))
@@ -5333,7 +5395,13 @@ def _maybe_inject_mandate_deliver(openai_body: dict, monitor: "SessionMonitor")
5333
5395
  return
5334
5396
  if _assistant_already_called_deliver(messages, deliver_name):
5335
5397
  return # deliver is already in flight -- don't loop on it
5336
- openai_body["tool_choice"] = {"type": "function", "function": {"name": deliver_name}}
5398
+ if not _pin_tool_choice_to(openai_body, deliver_name):
5399
+ # Could not actually constrain the turn (deliver absent from `tools`).
5400
+ # Don't claim a mandate that isn't real: recording one here would
5401
+ # suppress recon convergence while leaving the model free to hand-edit,
5402
+ # which is strictly worse than letting recon do its job.
5403
+ return
5404
+ monitor.mandate_deliver_active = True
5337
5405
  monitor.mandate_deliver_fires += 1
5338
5406
  directive = (
5339
5407
  "\n\nMANDATORY: your direct source edit was BLOCKED by delivery-enforcement. "
@@ -5419,6 +5487,31 @@ def _maybe_inject_recon_convergence(
5419
5487
  """
5420
5488
  if PROXY_RECON_CONVERGENCE_THRESHOLD <= 0:
5421
5489
  return
5490
+ # PRECEDENCE: the deliver mandate outranks recon convergence.
5491
+ #
5492
+ # Both run on the same turn (mandate first), and both rewrite tools/
5493
+ # tool_choice, so the later one wins by default -- which made the mandate's
5494
+ # own "runs before the softer guards so the pin stands" untrue. Until
5495
+ # v1.172.2 that was invisible: the pin was encoded in a form llama.cpp
5496
+ # ignored, so it had nothing to override.
5497
+ #
5498
+ # The mandate is the more specific and more recent signal: a source edit was
5499
+ # just BLOCKED and the only correct next action is a deliver call. Recon
5500
+ # exists to break a read-forever deadlock -- which cannot occur while the
5501
+ # mandate holds, because the pin leaves exactly one advertised tool under
5502
+ # tool_choice="required", so the model is constrained to call deliver rather
5503
+ # than read again. Letting recon win instead released that constraint and
5504
+ # handed back the freedom to keep hand-editing.
5505
+ #
5506
+ # Escape hatch if this ever wedges: PROXY_MANDATE_BEATS_RECON=off.
5507
+ if PROXY_MANDATE_BEATS_RECON and getattr(monitor, "mandate_deliver_active", False):
5508
+ logger.info(
5509
+ "RECON CONVERGENCE: suppressed -- deliver mandate owns this turn "
5510
+ "(no_write_streak=%d, mandate_fires=%d).",
5511
+ monitor.consecutive_no_write_turns,
5512
+ monitor.mandate_deliver_fires,
5513
+ )
5514
+ return
5422
5515
  streak = monitor.consecutive_no_write_turns
5423
5516
  if streak < PROXY_RECON_CONVERGENCE_THRESHOLD:
5424
5517
  return
@@ -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
- self.assertEqual(b["tool_choice"], {"type": "function", "function": {"name": "deliver"}})
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"], {"type": "function", "function": {"name": "mcp__uap-router__deliver"}})
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")