@miller-tech/uap 1.195.1 → 1.195.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.195.1",
3
+ "version": "1.195.2",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "start": "node dist/bin/cli.js",
22
22
  "test": "vitest",
23
23
  "test:ci": "vitest run",
24
- "test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_repeat_call_guard tools.agents.tests.test_gate_integrity tools.agents.tests.test_gate_failclosed_indirection tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_write_complexity tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core tools.agents.tests.test_models_context_window",
24
+ "test:enforcers": "UAP_PROXY_ENV_AUTOLOAD=0 python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_schema_diff_gate tools.agents.tests.test_stream_telemetry tools.agents.tests.test_project_telemetry_events tools.agents.tests.test_workdir_scope_enforcer tools.agents.tests.test_gate_evidence tools.agents.tests.test_repeat_call_guard tools.agents.tests.test_gate_integrity tools.agents.tests.test_gate_failclosed_indirection tools.agents.tests.test_expert_review_pr_scope tools.agents.tests.test_path_containment tools.agents.tests.test_path_normalizer_hardened tools.agents.tests.test_sandbox_tool_strip tools.agents.tests.test_proxy_env_loader tools.agents.tests.test_stream_required_tool tools.agents.tests.test_tool_call_wrapper_profiles tools.agents.tests.test_tool_convert_cache tools.agents.tests.test_doubling_break tools.agents.tests.test_error_loop_ignores_correctives tools.agents.tests.test_attractor_detection tools.agents.tests.test_client_disconnect tools.agents.tests.test_confidence_escalation tools.agents.tests.test_coordination_ban tools.agents.tests.test_coordination_early_ban tools.agents.tests.test_cycle_break_exploration tools.agents.tests.test_deferral_break tools.agents.tests.test_deliver_autoroute tools.agents.tests.test_delivery_enforcement_all_langs tools.agents.tests.test_delivery_enforcement_exemptions tools.agents.tests.test_delivery_enforcement_filepath tools.agents.tests.test_delivery_enforcement_write_complexity tools.agents.tests.test_delivery_enforcement_web_and_bash tools.agents.tests.test_disconnect_watcher tools.agents.tests.test_empty_maxtokens_recovery tools.agents.tests.test_empty_tool_loop_break tools.agents.tests.test_enforcer_escape_hatches tools.agents.tests.test_error_loop_break tools.agents.tests.test_finalize_suppression tools.agents.tests.test_malformed_unclosed_think tools.agents.tests.test_mandate_beats_recon tools.agents.tests.test_mandate_deliver tools.agents.tests.test_overflow_truncate_count_tokens tools.agents.tests.test_passthrough_oauth tools.agents.tests.test_project_telemetry tools.agents.tests.test_proxy_auth_headers tools.agents.tests.test_prune_preserve_force_write tools.agents.tests.test_recon_deliver_gate tools.agents.tests.test_session_admission tools.agents.tests.test_stream_heartbeat tools.agents.tests.test_stuck_break_reattach tools.agents.tests.test_turn_count_breaker_periodic tools.agents.tests.test_upstream_chokepoint tools.agents.tests.test_vision_passthrough tools.agents.tests.test_worktree_required tools.agents.tests.test_enforcer_suite_coverage tools.agents.tests.test_validate_plan_gate tools.agents.tests.test_validate_plan_inside_project tools.agents.tests.test_anthropic_proxy_streaming tools.agents.tests.test_non_stream_resp_log tools.agents.tests.test_delivery_enforcement_worktree tools.agents.tests.test_output_token_ceilings tools.agents.tests.test_tool_narrowing_core tools.agents.tests.test_models_context_window",
25
25
  "test:coverage": "vitest --coverage",
26
26
  "bench": "vitest --config vitest.bench.config.ts",
27
27
  "lint": "eslint src --ext .ts",
@@ -10504,6 +10504,43 @@ def _extract_thinking_block(text: str) -> tuple[str | None, str]:
10504
10504
  return "\n\n".join(p for p in parts if p), remaining.lstrip()
10505
10505
 
10506
10506
 
10507
+ def _log_non_stream_resp(content: list, finish: str, usage: dict) -> None:
10508
+ """Log what a NON-STREAMING turn produced, in the streaming line's shape.
10509
+
10510
+ ``RESP:`` was emitted only from ``stream_anthropic_response``, so every
10511
+ non-streaming turn logged a request and no outcome. That is not a small
10512
+ gap: ``uap deliver``'s agentic executor is entirely non-streaming, so the
10513
+ journal recorded 431 requests and ONE response during a three-hour run on
10514
+ 2026-08-11 — and diagnosing that run meant reconstructing what the model
10515
+ had done from the delivery log and the tool counts instead of reading it.
10516
+ A monitor that cannot see the answers cannot tell working from looping.
10517
+
10518
+ Same field order and prefix as the streaming line ON PURPOSE, so existing
10519
+ log analysis picks both up without a second parser; ``path=json`` is
10520
+ appended for anyone who needs to tell them apart.
10521
+ """
10522
+ text = "".join(
10523
+ b.get("text", "") for b in content if isinstance(b, dict) and b.get("type") == "text"
10524
+ )
10525
+ tool_names = [
10526
+ b.get("name") for b in content if isinstance(b, dict) and b.get("type") == "tool_use"
10527
+ ]
10528
+ tool_args = [
10529
+ json.dumps(b.get("input", {}), separators=(",", ":"))[:200]
10530
+ for b in content
10531
+ if isinstance(b, dict) and b.get("type") == "tool_use"
10532
+ ]
10533
+ logger.info(
10534
+ "RESP: finish=%s output_tokens=%d text_len=%d text=%.300s tool_calls=%s args=%s path=json",
10535
+ finish,
10536
+ usage.get("completion_tokens", 0),
10537
+ len(text),
10538
+ text[:300],
10539
+ tool_names,
10540
+ tool_args,
10541
+ )
10542
+
10543
+
10507
10544
  def openai_to_anthropic_response(
10508
10545
  openai_resp: dict,
10509
10546
  model: str,
@@ -10646,6 +10683,8 @@ def openai_to_anthropic_response(
10646
10683
 
10647
10684
  usage = openai_resp.get("usage", {})
10648
10685
 
10686
+ _log_non_stream_resp(content, finish, usage)
10687
+
10649
10688
  return {
10650
10689
  "id": f"msg_{uuid.uuid4().hex[:24]}",
10651
10690
  "type": "message",
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env python3
2
+ """A monitor that cannot see the answers cannot tell working from looping.
3
+
4
+ `RESP:` was logged only from `stream_anthropic_response`. `uap deliver`'s
5
+ agentic executor is entirely non-streaming, so during a three-hour run on
6
+ 2026-08-11 the journal held 431 requests and ONE response — and every one of
7
+ those 431 turns was invisible. Diagnosing that run meant reconstructing the
8
+ model's behaviour from the delivery log's tool counts instead of reading what
9
+ it actually returned.
10
+
11
+ These pin that a non-streaming turn now logs its outcome, in the same shape as
12
+ the streaming one so a single parser reads both.
13
+ """
14
+
15
+ import importlib.util
16
+ import json
17
+ import logging
18
+ import unittest
19
+ from pathlib import Path
20
+
21
+
22
+ def _load_proxy_module():
23
+ proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
24
+ spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
25
+ assert spec is not None and spec.loader is not None
26
+ module = importlib.util.module_from_spec(spec)
27
+ spec.loader.exec_module(module)
28
+ return module
29
+
30
+
31
+ proxy = _load_proxy_module()
32
+
33
+
34
+ def _resp(message, finish="stop", usage=None):
35
+ return {
36
+ "choices": [{"message": message, "finish_reason": finish}],
37
+ "usage": usage or {"prompt_tokens": 10, "completion_tokens": 7},
38
+ }
39
+
40
+
41
+ class NonStreamRespLog(unittest.TestCase):
42
+ def _convert(self, openai_resp):
43
+ with self.assertLogs("uap.anthropic_proxy", level="INFO") as captured:
44
+ out = proxy.openai_to_anthropic_response(openai_resp, "qwen")
45
+ return out, [r for r in captured.output if "RESP: finish=" in r]
46
+
47
+ def test_text_turn_logs_its_outcome(self):
48
+ _, lines = self._convert(_resp({"role": "assistant", "content": "hello world"}))
49
+ self.assertEqual(len(lines), 1, "a non-streaming turn must log exactly one RESP")
50
+ line = lines[0]
51
+ self.assertIn("finish=stop", line)
52
+ self.assertIn("output_tokens=7", line)
53
+ self.assertIn("text_len=11", line)
54
+ self.assertIn("hello world", line)
55
+
56
+ def test_tool_turn_names_the_tool_and_its_args(self):
57
+ # The whole point: "what did it DO this round" has to be readable.
58
+ _, lines = self._convert(
59
+ _resp(
60
+ {
61
+ "role": "assistant",
62
+ "content": None,
63
+ "tool_calls": [
64
+ {
65
+ "id": "call_1",
66
+ "type": "function",
67
+ "function": {
68
+ "name": "read_file",
69
+ "arguments": json.dumps({"path": "setup.sql", "offset": 607}),
70
+ },
71
+ }
72
+ ],
73
+ },
74
+ finish="tool_calls",
75
+ )
76
+ )
77
+ self.assertEqual(len(lines), 1)
78
+ self.assertIn("read_file", lines[0])
79
+ self.assertIn("setup.sql", lines[0])
80
+ self.assertIn("607", lines[0], "the args are what distinguish paging from a re-read")
81
+
82
+ def test_empty_completion_is_visible_as_empty(self):
83
+ # An empty completion is a failure mode with its own history here
84
+ # (decode-compliance/budget truncation). It must not read as silence.
85
+ _, lines = self._convert(
86
+ _resp({"role": "assistant", "content": ""}, finish="length",
87
+ usage={"prompt_tokens": 5, "completion_tokens": 0})
88
+ )
89
+ self.assertEqual(len(lines), 1)
90
+ self.assertIn("text_len=0", lines[0])
91
+ self.assertIn("finish=length", lines[0])
92
+
93
+ def test_shape_matches_the_streaming_line_so_one_parser_reads_both(self):
94
+ _, lines = self._convert(_resp({"role": "assistant", "content": "x"}))
95
+ for field in ("RESP: finish=", "output_tokens=", "text_len=", "text=", "tool_calls=", "args="):
96
+ self.assertIn(field, lines[0], field)
97
+ self.assertIn("path=json", lines[0], "…while still being distinguishable")
98
+
99
+ def test_logging_never_breaks_the_conversion(self):
100
+ # Fail-soft: the response is the product, the log line is not. A
101
+ # malformed content block must not cost the client its answer.
102
+ out, _ = self._convert(_resp({"role": "assistant", "content": "fine"}))
103
+ self.assertEqual(out["role"], "assistant")
104
+ self.assertEqual(out["content"][0]["text"], "fine")
105
+ self.assertEqual(out["stop_reason"], "end_turn")
106
+
107
+
108
+ class RespLogSurvivesOddContent(unittest.TestCase):
109
+ def test_non_dict_content_blocks_do_not_raise(self):
110
+ logging.getLogger("uap.anthropic_proxy").setLevel(logging.INFO)
111
+ # Defensive: content is assembled upstream and has been non-uniform
112
+ # before (thinking promotion, text-tool extraction).
113
+ proxy._log_non_stream_resp(["not a dict", {"type": "text", "text": "ok"}], "stop", {})
114
+
115
+
116
+ if __name__ == "__main__":
117
+ unittest.main()