@miller-tech/uap 1.59.0 → 1.61.0
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/benchmarks/paired/adapter.d.ts.map +1 -1
- package/dist/benchmarks/paired/adapter.js +14 -1
- package/dist/benchmarks/paired/adapter.js.map +1 -1
- package/dist/benchmarks/paired/scaffold.d.ts.map +1 -1
- package/dist/benchmarks/paired/scaffold.js +6 -1
- package/dist/benchmarks/paired/scaffold.js.map +1 -1
- package/dist/bin/cli.js +58 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/deliver.d.ts +33 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +90 -7
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/self-harness.d.ts +36 -0
- package/dist/cli/self-harness.d.ts.map +1 -0
- package/dist/cli/self-harness.js +213 -0
- package/dist/cli/self-harness.js.map +1 -0
- package/dist/mcp-router/executor/client.d.ts.map +1 -1
- package/dist/mcp-router/executor/client.js +16 -2
- package/dist/mcp-router/executor/client.js.map +1 -1
- package/dist/mcp-router/output-compressor.d.ts.map +1 -1
- package/dist/mcp-router/output-compressor.js +51 -0
- package/dist/mcp-router/output-compressor.js.map +1 -1
- package/dist/self-harness/decide.d.ts +41 -0
- package/dist/self-harness/decide.d.ts.map +1 -0
- package/dist/self-harness/decide.js +69 -0
- package/dist/self-harness/decide.js.map +1 -0
- package/dist/self-harness/index.d.ts +23 -0
- package/dist/self-harness/index.d.ts.map +1 -0
- package/dist/self-harness/index.js +23 -0
- package/dist/self-harness/index.js.map +1 -0
- package/dist/self-harness/middleware/path-normalizer.d.ts +53 -0
- package/dist/self-harness/middleware/path-normalizer.d.ts.map +1 -0
- package/dist/self-harness/middleware/path-normalizer.js +116 -0
- package/dist/self-harness/middleware/path-normalizer.js.map +1 -0
- package/dist/self-harness/mine.d.ts +25 -0
- package/dist/self-harness/mine.d.ts.map +1 -0
- package/dist/self-harness/mine.js +78 -0
- package/dist/self-harness/mine.js.map +1 -0
- package/dist/self-harness/mods.d.ts +136 -0
- package/dist/self-harness/mods.d.ts.map +1 -0
- package/dist/self-harness/mods.js +152 -0
- package/dist/self-harness/mods.js.map +1 -0
- package/dist/self-harness/orchestrator.d.ts +61 -0
- package/dist/self-harness/orchestrator.d.ts.map +1 -0
- package/dist/self-harness/orchestrator.js +110 -0
- package/dist/self-harness/orchestrator.js.map +1 -0
- package/dist/self-harness/pending.d.ts +57 -0
- package/dist/self-harness/pending.d.ts.map +1 -0
- package/dist/self-harness/pending.js +86 -0
- package/dist/self-harness/pending.js.map +1 -0
- package/dist/self-harness/profile.d.ts +38 -0
- package/dist/self-harness/profile.d.ts.map +1 -0
- package/dist/self-harness/profile.js +80 -0
- package/dist/self-harness/profile.js.map +1 -0
- package/dist/self-harness/propose.d.ts +25 -0
- package/dist/self-harness/propose.d.ts.map +1 -0
- package/dist/self-harness/propose.js +86 -0
- package/dist/self-harness/propose.js.map +1 -0
- package/dist/self-harness/trace-mine.d.ts +39 -0
- package/dist/self-harness/trace-mine.d.ts.map +1 -0
- package/dist/self-harness/trace-mine.js +109 -0
- package/dist/self-harness/trace-mine.js.map +1 -0
- package/dist/self-harness/transfer.d.ts +99 -0
- package/dist/self-harness/transfer.d.ts.map +1 -0
- package/dist/self-harness/transfer.js +140 -0
- package/dist/self-harness/transfer.js.map +1 -0
- package/dist/self-harness/weakness.d.ts +56 -0
- package/dist/self-harness/weakness.d.ts.map +1 -0
- package/dist/self-harness/weakness.js +0 -0
- package/dist/self-harness/weakness.js.map +1 -0
- package/dist/tasks/coordination.d.ts.map +1 -1
- package/dist/tasks/coordination.js +9 -7
- package/dist/tasks/coordination.js.map +1 -1
- package/dist/tasks/service.d.ts +10 -0
- package/dist/tasks/service.d.ts.map +1 -1
- package/dist/tasks/service.js +41 -5
- package/dist/tasks/service.js.map +1 -1
- package/docs/INDEX.md +1 -0
- package/docs/design/SELF_HARNESS.md +206 -0
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +160 -18
- package/tools/agents/scripts/toolcall_path_normalizer.py +140 -0
- package/tools/agents/tests/test_anthropic_proxy_streaming.py +93 -2
|
@@ -1809,6 +1809,64 @@ class TestMalformedToolGuardrail(unittest.TestCase):
|
|
|
1809
1809
|
self.assertEqual(len(fake_client.requests), 1)
|
|
1810
1810
|
|
|
1811
1811
|
|
|
1812
|
+
class TestTurnCountFinalizeBreaker(unittest.TestCase):
|
|
1813
|
+
"""The mode-B runaway backstop: after PROXY_HARD_FINALIZE_TURNS assistant
|
|
1814
|
+
tool turns, the proxy strips tools so the only possible output is a terminal
|
|
1815
|
+
text summary (end_turn). Inert below the ceiling."""
|
|
1816
|
+
|
|
1817
|
+
@staticmethod
|
|
1818
|
+
def _body(n_tool_turns):
|
|
1819
|
+
msgs = [{"role": "user", "content": "do the task"}]
|
|
1820
|
+
for i in range(n_tool_turns):
|
|
1821
|
+
msgs.append({
|
|
1822
|
+
"role": "assistant",
|
|
1823
|
+
"content": [{"type": "tool_use", "id": f"t{i}", "name": "Bash",
|
|
1824
|
+
"input": {"command": "ls"}}],
|
|
1825
|
+
})
|
|
1826
|
+
msgs.append({
|
|
1827
|
+
"role": "user",
|
|
1828
|
+
"content": [{"type": "tool_result", "tool_use_id": f"t{i}", "content": "ok"}],
|
|
1829
|
+
})
|
|
1830
|
+
return {
|
|
1831
|
+
"model": "x", "messages": msgs,
|
|
1832
|
+
"tools": [{"name": "Bash", "input_schema": {"type": "object"}}],
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
def test_strips_tools_above_ceiling(self):
|
|
1836
|
+
old = getattr(proxy, "PROXY_HARD_FINALIZE_TURNS")
|
|
1837
|
+
try:
|
|
1838
|
+
setattr(proxy, "PROXY_HARD_FINALIZE_TURNS", 40)
|
|
1839
|
+
out = proxy.build_openai_request(
|
|
1840
|
+
self._body(45), proxy.SessionMonitor(context_window=262144)
|
|
1841
|
+
)
|
|
1842
|
+
self.assertFalse(out.get("tools")) # tools stripped
|
|
1843
|
+
self.assertIn("STOP now", out["messages"][-1]["content"])
|
|
1844
|
+
finally:
|
|
1845
|
+
setattr(proxy, "PROXY_HARD_FINALIZE_TURNS", old)
|
|
1846
|
+
|
|
1847
|
+
def test_inert_below_ceiling(self):
|
|
1848
|
+
old = getattr(proxy, "PROXY_HARD_FINALIZE_TURNS")
|
|
1849
|
+
try:
|
|
1850
|
+
setattr(proxy, "PROXY_HARD_FINALIZE_TURNS", 40)
|
|
1851
|
+
out = proxy.build_openai_request(
|
|
1852
|
+
self._body(5), proxy.SessionMonitor(context_window=262144)
|
|
1853
|
+
)
|
|
1854
|
+
self.assertTrue(out.get("tools")) # tools retained
|
|
1855
|
+
finally:
|
|
1856
|
+
setattr(proxy, "PROXY_HARD_FINALIZE_TURNS", old)
|
|
1857
|
+
|
|
1858
|
+
def test_disabled_when_zero(self):
|
|
1859
|
+
old = getattr(proxy, "PROXY_HARD_FINALIZE_TURNS")
|
|
1860
|
+
try:
|
|
1861
|
+
setattr(proxy, "PROXY_HARD_FINALIZE_TURNS", 0)
|
|
1862
|
+
out = proxy.build_openai_request(
|
|
1863
|
+
self._body(60), proxy.SessionMonitor(context_window=262144)
|
|
1864
|
+
)
|
|
1865
|
+
self.assertTrue(out.get("tools")) # disabled → never strips
|
|
1866
|
+
finally:
|
|
1867
|
+
setattr(proxy, "PROXY_HARD_FINALIZE_TURNS", old)
|
|
1868
|
+
|
|
1869
|
+
|
|
1812
1870
|
class TestToolTurnControls(unittest.TestCase):
|
|
1813
1871
|
def test_tool_narrowing_reduces_tool_count(self):
|
|
1814
1872
|
old_narrow = getattr(proxy, "PROXY_TOOL_NARROWING")
|
|
@@ -1853,9 +1911,17 @@ class TestToolTurnControls(unittest.TestCase):
|
|
|
1853
1911
|
openai = proxy.build_openai_request(
|
|
1854
1912
|
body, proxy.SessionMonitor(context_window=262144)
|
|
1855
1913
|
)
|
|
1856
|
-
self.assertEqual(len(openai.get("tools", [])), 2)
|
|
1857
1914
|
names = [t.get("function", {}).get("name") for t in openai.get("tools", [])]
|
|
1858
|
-
|
|
1915
|
+
# Narrowing must still REDUCE the toolset by dropping irrelevant tools
|
|
1916
|
+
# (Deploy), while keeping the query-relevant tool (RunTests) AND core
|
|
1917
|
+
# action tools (Read/Edit) — a coding agent that loses Edit cannot
|
|
1918
|
+
# "fix the failing test". Core-tool protection makes `keep` a soft
|
|
1919
|
+
# floor, so the exact count is keep + any core tools not already in
|
|
1920
|
+
# the top-scored set; assert the invariant, not a brittle fixed count.
|
|
1921
|
+
self.assertLess(len(names), 4) # reduced from 4
|
|
1922
|
+
self.assertIn("RunTests", names) # query-relevant kept
|
|
1923
|
+
self.assertIn("Edit", names) # core action tool kept
|
|
1924
|
+
self.assertNotIn("Deploy", names) # irrelevant dropped
|
|
1859
1925
|
finally:
|
|
1860
1926
|
setattr(proxy, "PROXY_TOOL_NARROWING", old_narrow)
|
|
1861
1927
|
setattr(proxy, "PROXY_TOOL_NARROWING_KEEP", old_keep)
|
|
@@ -5480,6 +5546,31 @@ class TestReconConvergence(unittest.TestCase):
|
|
|
5480
5546
|
proxy._maybe_inject_recon_convergence(body, m)
|
|
5481
5547
|
self.assertEqual(len(body["messages"]), 1)
|
|
5482
5548
|
|
|
5549
|
+
def test_escalate_is_one_shot_not_a_permanent_poison(self):
|
|
5550
|
+
"""POISON FIX: the escalate tier strips tools to force a terminal
|
|
5551
|
+
summary, but it must reset the no-write streak so it does NOT re-fire
|
|
5552
|
+
every subsequent request. Without the reset, recon_hard_fires is
|
|
5553
|
+
monotonic (escalate stays true) and stripped tools mean the model can
|
|
5554
|
+
never write (streak never falls), so EVERY request gets tools stripped
|
|
5555
|
+
forever — poisoning the session (and any colliding-fingerprint session)
|
|
5556
|
+
until a proxy restart."""
|
|
5557
|
+
proxy.PROXY_RECON_CONVERGENCE_THRESHOLD = 40
|
|
5558
|
+
m = proxy.SessionMonitor(context_window=131072)
|
|
5559
|
+
tools = [self._tool("Write"), self._tool("Read")]
|
|
5560
|
+
# drive into escalate: streak huge, hard-tier already fired to the cap
|
|
5561
|
+
m.consecutive_no_write_turns = 200
|
|
5562
|
+
m.recon_hard_fires = proxy.PROXY_RECON_SESSION_HARD_CAP
|
|
5563
|
+
b1 = {"messages": [{"role": "user", "content": "go"}],
|
|
5564
|
+
"tools": list(tools), "tool_choice": "required"}
|
|
5565
|
+
proxy._maybe_inject_recon_convergence(b1, m, tools)
|
|
5566
|
+
self.assertNotIn("tools", b1) # escalate stripped tools
|
|
5567
|
+
self.assertEqual(m.consecutive_no_write_turns, 0) # streak reset (the fix)
|
|
5568
|
+
# next request: streak is now below threshold → MUST NOT strip again
|
|
5569
|
+
b2 = {"messages": [{"role": "user", "content": "go"}],
|
|
5570
|
+
"tools": list(tools), "tool_choice": "required"}
|
|
5571
|
+
proxy._maybe_inject_recon_convergence(b2, m, tools)
|
|
5572
|
+
self.assertTrue(b2.get("tools")) # tools restored, not poisoned
|
|
5573
|
+
|
|
5483
5574
|
@staticmethod
|
|
5484
5575
|
def _tool(name: str) -> dict:
|
|
5485
5576
|
return {"type": "function", "function": {"name": name, "description": f"{name} tool"}}
|