@miller-tech/uap 1.76.1 → 1.76.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.76.1",
3
+ "version": "1.76.2",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -6339,6 +6339,22 @@ def _looks_malformed_tool_payload(text: str) -> bool:
6339
6339
  if not text.strip():
6340
6340
  return False
6341
6341
 
6342
+ # 2026-06-27: Also strip a trailing UNCLOSED <think> (opener present, no
6343
+ # matching </think>). Under `--reasoning auto` the model frequently runs out
6344
+ # of its token budget mid-reasoning, emitting
6345
+ # "<think> ...let me write files via multiple sandbox calls... args="
6346
+ # with no </think>. That is TRUNCATED reasoning, not a malformed tool call —
6347
+ # but the balanced-only strip above left it intact, so the meta-tool talk
6348
+ # inside it tripped the structural-marker / apology branches below
6349
+ # (false-positive malformed_payload that stalled agentic builds, e.g. the
6350
+ # Octopus Invaders generation: ~11 false rejections in 40 min). Drop from the
6351
+ # first opener to end; KEEP any text before it so a genuine malformed payload
6352
+ # preceding the reasoning is still detected.
6353
+ if "<think>" in text and "</think>" not in text:
6354
+ text = text[: text.index("<think>")]
6355
+ if not text.strip():
6356
+ return False
6357
+
6342
6358
  # 2026-05-12: Strip orphan </parameter> and </function> closers that
6343
6359
  # have no matching opener. Qwen3.6 leaks these training residuals
6344
6360
  # after its visible answer when forced into tool_choice='required'
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ """An UNCLOSED <think> block must NOT be classified as a malformed tool payload.
3
+
4
+ Under llama-server `--reasoning auto`, Qwen3.6 emits <think> reasoning. When it
5
+ runs out of its token budget mid-thought it produces an *unclosed* block, e.g.
6
+
7
+ "<think> The sandbox works. Now write the remaining files via multiple
8
+ sandbox calls... args="
9
+
10
+ with no </think>. That is TRUNCATED reasoning, not a malformed tool call — but
11
+ the prior detector only stripped *balanced* <think>...</think>, so the meta-tool
12
+ talk inside the unclosed block tripped the structural-marker branch and the proxy
13
+ rejected/retried the turn (observed: ~11 false rejections in 40 min stalling the
14
+ Octopus Invaders agentic build). `_looks_malformed_tool_payload` now also strips a
15
+ trailing unclosed <think> (keeping any text BEFORE the opener so a genuine
16
+ malformed payload preceding the reasoning is still caught).
17
+ """
18
+
19
+ import importlib.util
20
+ import unittest
21
+ from pathlib import Path
22
+
23
+
24
+ def _load_proxy_module():
25
+ proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
26
+ spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
27
+ module = importlib.util.module_from_spec(spec)
28
+ spec.loader.exec_module(module)
29
+ return module
30
+
31
+
32
+ proxy = _load_proxy_module()
33
+ _malformed = proxy._looks_malformed_tool_payload
34
+
35
+
36
+ class TestUnclosedThinkNotMalformed(unittest.TestCase):
37
+ def test_unclosed_think_with_meta_tool_talk_is_not_malformed(self):
38
+ # The exact failure shape that stalled the Octopus build.
39
+ text = (
40
+ "<think> The sandbox approach works. Now I need to write all the "
41
+ "remaining files. Let me write them efficiently using multiple "
42
+ "sandbox calls in parallel. I'll write the files in dependency "
43
+ "order: 1. config.js (done) 2. args="
44
+ )
45
+ self.assertFalse(_malformed(text))
46
+
47
+ def test_unclosed_think_with_function_words_is_not_malformed(self):
48
+ # meta-tool talk that mentions <function= / <parameter inside reasoning
49
+ text = "<think> I should call <function=Write> with <parameter=path>..."
50
+ self.assertFalse(_malformed(text))
51
+
52
+ def test_balanced_think_still_not_malformed(self):
53
+ text = "<think> planning... </think>"
54
+ self.assertFalse(_malformed(text))
55
+
56
+ def test_genuine_malformed_payload_without_think_still_detected(self):
57
+ # No <think> at all — a real leaked tool-call fragment must still trip.
58
+ self.assertTrue(_malformed("<function=Write><parameter=path>x</parameter>"))
59
+
60
+ def test_malformed_payload_BEFORE_unclosed_think_still_detected(self):
61
+ # Keep text before the opener: a real malformed payload preceding the
62
+ # reasoning must still be caught.
63
+ text = "<function=Write><parameter=p>v</parameter> <think> now reasoning..."
64
+ self.assertTrue(_malformed(text))
65
+
66
+ def test_clean_prose_with_no_tool_markup_is_not_malformed(self):
67
+ self.assertFalse(_malformed("Here is the summary of what I did."))
68
+
69
+
70
+ if __name__ == "__main__":
71
+ unittest.main()