@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.
Files changed (85) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/benchmarks/paired/adapter.d.ts.map +1 -1
  3. package/dist/benchmarks/paired/adapter.js +14 -1
  4. package/dist/benchmarks/paired/adapter.js.map +1 -1
  5. package/dist/benchmarks/paired/scaffold.d.ts.map +1 -1
  6. package/dist/benchmarks/paired/scaffold.js +6 -1
  7. package/dist/benchmarks/paired/scaffold.js.map +1 -1
  8. package/dist/bin/cli.js +58 -0
  9. package/dist/bin/cli.js.map +1 -1
  10. package/dist/cli/deliver.d.ts +33 -0
  11. package/dist/cli/deliver.d.ts.map +1 -1
  12. package/dist/cli/deliver.js +90 -7
  13. package/dist/cli/deliver.js.map +1 -1
  14. package/dist/cli/self-harness.d.ts +36 -0
  15. package/dist/cli/self-harness.d.ts.map +1 -0
  16. package/dist/cli/self-harness.js +213 -0
  17. package/dist/cli/self-harness.js.map +1 -0
  18. package/dist/mcp-router/executor/client.d.ts.map +1 -1
  19. package/dist/mcp-router/executor/client.js +16 -2
  20. package/dist/mcp-router/executor/client.js.map +1 -1
  21. package/dist/mcp-router/output-compressor.d.ts.map +1 -1
  22. package/dist/mcp-router/output-compressor.js +51 -0
  23. package/dist/mcp-router/output-compressor.js.map +1 -1
  24. package/dist/self-harness/decide.d.ts +41 -0
  25. package/dist/self-harness/decide.d.ts.map +1 -0
  26. package/dist/self-harness/decide.js +69 -0
  27. package/dist/self-harness/decide.js.map +1 -0
  28. package/dist/self-harness/index.d.ts +23 -0
  29. package/dist/self-harness/index.d.ts.map +1 -0
  30. package/dist/self-harness/index.js +23 -0
  31. package/dist/self-harness/index.js.map +1 -0
  32. package/dist/self-harness/middleware/path-normalizer.d.ts +53 -0
  33. package/dist/self-harness/middleware/path-normalizer.d.ts.map +1 -0
  34. package/dist/self-harness/middleware/path-normalizer.js +116 -0
  35. package/dist/self-harness/middleware/path-normalizer.js.map +1 -0
  36. package/dist/self-harness/mine.d.ts +25 -0
  37. package/dist/self-harness/mine.d.ts.map +1 -0
  38. package/dist/self-harness/mine.js +78 -0
  39. package/dist/self-harness/mine.js.map +1 -0
  40. package/dist/self-harness/mods.d.ts +136 -0
  41. package/dist/self-harness/mods.d.ts.map +1 -0
  42. package/dist/self-harness/mods.js +152 -0
  43. package/dist/self-harness/mods.js.map +1 -0
  44. package/dist/self-harness/orchestrator.d.ts +61 -0
  45. package/dist/self-harness/orchestrator.d.ts.map +1 -0
  46. package/dist/self-harness/orchestrator.js +110 -0
  47. package/dist/self-harness/orchestrator.js.map +1 -0
  48. package/dist/self-harness/pending.d.ts +57 -0
  49. package/dist/self-harness/pending.d.ts.map +1 -0
  50. package/dist/self-harness/pending.js +86 -0
  51. package/dist/self-harness/pending.js.map +1 -0
  52. package/dist/self-harness/profile.d.ts +38 -0
  53. package/dist/self-harness/profile.d.ts.map +1 -0
  54. package/dist/self-harness/profile.js +80 -0
  55. package/dist/self-harness/profile.js.map +1 -0
  56. package/dist/self-harness/propose.d.ts +25 -0
  57. package/dist/self-harness/propose.d.ts.map +1 -0
  58. package/dist/self-harness/propose.js +86 -0
  59. package/dist/self-harness/propose.js.map +1 -0
  60. package/dist/self-harness/trace-mine.d.ts +39 -0
  61. package/dist/self-harness/trace-mine.d.ts.map +1 -0
  62. package/dist/self-harness/trace-mine.js +109 -0
  63. package/dist/self-harness/trace-mine.js.map +1 -0
  64. package/dist/self-harness/transfer.d.ts +99 -0
  65. package/dist/self-harness/transfer.d.ts.map +1 -0
  66. package/dist/self-harness/transfer.js +140 -0
  67. package/dist/self-harness/transfer.js.map +1 -0
  68. package/dist/self-harness/weakness.d.ts +56 -0
  69. package/dist/self-harness/weakness.d.ts.map +1 -0
  70. package/dist/self-harness/weakness.js +0 -0
  71. package/dist/self-harness/weakness.js.map +1 -0
  72. package/dist/tasks/coordination.d.ts.map +1 -1
  73. package/dist/tasks/coordination.js +9 -7
  74. package/dist/tasks/coordination.js.map +1 -1
  75. package/dist/tasks/service.d.ts +10 -0
  76. package/dist/tasks/service.d.ts.map +1 -1
  77. package/dist/tasks/service.js +41 -5
  78. package/dist/tasks/service.js.map +1 -1
  79. package/docs/INDEX.md +1 -0
  80. package/docs/design/SELF_HARNESS.md +206 -0
  81. package/package.json +1 -1
  82. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  83. package/tools/agents/scripts/anthropic_proxy.py +160 -18
  84. package/tools/agents/scripts/toolcall_path_normalizer.py +140 -0
  85. 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
- self.assertIn("RunTests", names)
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"}}