@miller-tech/uap 1.221.0 → 2.0.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 (238) hide show
  1. package/README.md +19 -9
  2. package/config/exl3-profiles/qwen38-27b-exl3.env +68 -0
  3. package/config/llama-profiles/qwen38-27b-dflash2.env +6 -1
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/benchmarks/paired/adapter.d.ts +15 -0
  6. package/dist/benchmarks/paired/adapter.d.ts.map +1 -1
  7. package/dist/benchmarks/paired/adapter.js +22 -1
  8. package/dist/benchmarks/paired/adapter.js.map +1 -1
  9. package/dist/bin/cli.js +20 -24
  10. package/dist/bin/cli.js.map +1 -1
  11. package/dist/cli/deliver.d.ts +34 -0
  12. package/dist/cli/deliver.d.ts.map +1 -1
  13. package/dist/cli/deliver.js +147 -20
  14. package/dist/cli/deliver.js.map +1 -1
  15. package/dist/cli/guided-setup.d.ts.map +1 -1
  16. package/dist/cli/guided-setup.js +15 -0
  17. package/dist/cli/guided-setup.js.map +1 -1
  18. package/dist/cli/init.d.ts.map +1 -1
  19. package/dist/cli/init.js +3 -0
  20. package/dist/cli/init.js.map +1 -1
  21. package/dist/cli/plan.d.ts.map +1 -1
  22. package/dist/cli/plan.js +8 -2
  23. package/dist/cli/plan.js.map +1 -1
  24. package/dist/cli/policy.d.ts.map +1 -1
  25. package/dist/cli/policy.js +104 -2
  26. package/dist/cli/policy.js.map +1 -1
  27. package/dist/cli/quality.d.ts +11 -0
  28. package/dist/cli/quality.d.ts.map +1 -0
  29. package/dist/cli/quality.js +213 -0
  30. package/dist/cli/quality.js.map +1 -0
  31. package/dist/cli/wizard-config.d.ts.map +1 -1
  32. package/dist/cli/wizard-config.js +19 -9
  33. package/dist/cli/wizard-config.js.map +1 -1
  34. package/dist/cli/worktree.d.ts.map +1 -1
  35. package/dist/cli/worktree.js +8 -1
  36. package/dist/cli/worktree.js.map +1 -1
  37. package/dist/dashboard/savings.d.ts +1 -4
  38. package/dist/dashboard/savings.d.ts.map +1 -1
  39. package/dist/dashboard/savings.js +2 -52
  40. package/dist/dashboard/savings.js.map +1 -1
  41. package/dist/delivery/acceptance-judge.d.ts +10 -0
  42. package/dist/delivery/acceptance-judge.d.ts.map +1 -1
  43. package/dist/delivery/acceptance-judge.js +45 -3
  44. package/dist/delivery/acceptance-judge.js.map +1 -1
  45. package/dist/delivery/agentic-executor.d.ts +12 -1
  46. package/dist/delivery/agentic-executor.d.ts.map +1 -1
  47. package/dist/delivery/agentic-executor.js +25 -5
  48. package/dist/delivery/agentic-executor.js.map +1 -1
  49. package/dist/delivery/bash-sweep.d.ts +12 -0
  50. package/dist/delivery/bash-sweep.d.ts.map +1 -1
  51. package/dist/delivery/bash-sweep.js +8 -1
  52. package/dist/delivery/bash-sweep.js.map +1 -1
  53. package/dist/delivery/convergence-loop.d.ts.map +1 -1
  54. package/dist/delivery/convergence-loop.js +6 -4
  55. package/dist/delivery/convergence-loop.js.map +1 -1
  56. package/dist/delivery/execution-gate.d.ts +1 -1
  57. package/dist/delivery/execution-gate.d.ts.map +1 -1
  58. package/dist/delivery/execution-gate.js +248 -5
  59. package/dist/delivery/execution-gate.js.map +1 -1
  60. package/dist/delivery/snapshot.d.ts +16 -0
  61. package/dist/delivery/snapshot.d.ts.map +1 -1
  62. package/dist/delivery/snapshot.js +113 -2
  63. package/dist/delivery/snapshot.js.map +1 -1
  64. package/dist/delivery/user-validation.d.ts.map +1 -1
  65. package/dist/delivery/user-validation.js +55 -10
  66. package/dist/delivery/user-validation.js.map +1 -1
  67. package/dist/delivery/verifier-ladder.d.ts +24 -0
  68. package/dist/delivery/verifier-ladder.d.ts.map +1 -1
  69. package/dist/delivery/verifier-ladder.js +119 -2
  70. package/dist/delivery/verifier-ladder.js.map +1 -1
  71. package/dist/delivery/visual-gate.d.ts +24 -0
  72. package/dist/delivery/visual-gate.d.ts.map +1 -1
  73. package/dist/delivery/visual-gate.js +70 -9
  74. package/dist/delivery/visual-gate.js.map +1 -1
  75. package/dist/generators/claude-md.js +17 -4
  76. package/dist/generators/claude-md.js.map +1 -1
  77. package/dist/policies/liveness.d.ts +105 -0
  78. package/dist/policies/liveness.d.ts.map +1 -0
  79. package/dist/policies/liveness.js +251 -0
  80. package/dist/policies/liveness.js.map +1 -0
  81. package/dist/policies/policy-memory.d.ts.map +1 -1
  82. package/dist/policies/policy-memory.js +3 -4
  83. package/dist/policies/policy-memory.js.map +1 -1
  84. package/dist/quality/baseline.d.ts +29 -0
  85. package/dist/quality/baseline.d.ts.map +1 -0
  86. package/dist/quality/baseline.js +79 -0
  87. package/dist/quality/baseline.js.map +1 -0
  88. package/dist/quality/complexity.d.ts +38 -0
  89. package/dist/quality/complexity.d.ts.map +1 -0
  90. package/dist/quality/complexity.js +239 -0
  91. package/dist/quality/complexity.js.map +1 -0
  92. package/dist/quality/config.d.ts +40 -0
  93. package/dist/quality/config.d.ts.map +1 -0
  94. package/dist/quality/config.js +77 -0
  95. package/dist/quality/config.js.map +1 -0
  96. package/dist/quality/crap.d.ts +11 -0
  97. package/dist/quality/crap.d.ts.map +1 -0
  98. package/dist/quality/crap.js +54 -0
  99. package/dist/quality/crap.js.map +1 -0
  100. package/dist/quality/languages.d.ts +25 -0
  101. package/dist/quality/languages.d.ts.map +1 -0
  102. package/dist/quality/languages.js +150 -0
  103. package/dist/quality/languages.js.map +1 -0
  104. package/dist/quality/report.d.ts +38 -0
  105. package/dist/quality/report.d.ts.map +1 -0
  106. package/dist/quality/report.js +251 -0
  107. package/dist/quality/report.js.map +1 -0
  108. package/dist/quality/scanner.d.ts +32 -0
  109. package/dist/quality/scanner.d.ts.map +1 -0
  110. package/dist/quality/scanner.js +127 -0
  111. package/dist/quality/scanner.js.map +1 -0
  112. package/dist/quality/tools.d.ts +28 -0
  113. package/dist/quality/tools.d.ts.map +1 -0
  114. package/dist/quality/tools.js +189 -0
  115. package/dist/quality/tools.js.map +1 -0
  116. package/dist/types/config.d.ts +148 -0
  117. package/dist/types/config.d.ts.map +1 -1
  118. package/dist/types/config.js +35 -0
  119. package/dist/types/config.js.map +1 -1
  120. package/docs/ATTRIBUTION.md +0 -1
  121. package/docs/INDEX.md +38 -4
  122. package/docs/analysis/MARKET_POSITION.md +179 -0
  123. package/docs/architecture/OVERVIEW.md +82 -18
  124. package/docs/architecture/PROTOCOL.md +1 -1
  125. package/docs/benchmarks/PAIRED_FINDINGS.md +106 -0
  126. package/docs/benchmarks/PAIRED_HARNESS.md +16 -0
  127. package/docs/benchmarks/README.md +59 -0
  128. package/docs/design/UAP_REACTOR.md +0 -1
  129. package/docs/getting-started/INSTALLATION.md +1 -1
  130. package/docs/guides/AUTOMATIC.md +0 -14
  131. package/docs/guides/AUTOMATIC_FEATURES.md +1 -9
  132. package/docs/guides/COORDINATION.md +1 -1
  133. package/docs/guides/DELIVERY_PIPELINE.md +5 -0
  134. package/docs/guides/DEPLOY_BATCHING.md +1 -1
  135. package/docs/guides/DROIDS_AND_SKILLS.md +1 -1
  136. package/docs/guides/LOCAL_MODELS.md +6 -1
  137. package/docs/guides/MCP_ROUTER.md +1 -1
  138. package/docs/guides/MEMORY.md +1 -1
  139. package/docs/guides/MULTI_MODEL.md +1 -1
  140. package/docs/guides/ORCHESTRATOR.md +1 -1
  141. package/docs/guides/POLICIES.md +19 -3
  142. package/docs/guides/QWEN38_EXL3.md +114 -0
  143. package/docs/guides/WORKTREE_WORKFLOW.md +45 -1
  144. package/docs/integrations/MCP_ROUTER.md +1 -4
  145. package/docs/plans/deliver-hardening-plan-2026-07-13.md +173 -0
  146. package/docs/plans/deliver-hardening-review-response-2026-07-13.md +180 -0
  147. package/docs/plans/k3-uap-uplift-validation.md +216 -0
  148. package/docs/plans/qwen38-uap-retest-plan.md +196 -0
  149. package/docs/reference/API.md +1 -1
  150. package/docs/reference/CLI.md +49 -14
  151. package/docs/reference/COMPARISON.md +248 -0
  152. package/docs/reference/CONFIGURATION.md +1 -3
  153. package/docs/reference/DATABASE_SCHEMA.md +1 -1
  154. package/docs/reference/FEATURES.md +3 -3
  155. package/docs/reference/PATTERNS.md +1 -1
  156. package/docs/reference/PLATFORMS.md +1 -1
  157. package/package.json +2 -2
  158. package/scripts/maintenance/update-skills.py +0 -22
  159. package/scripts/maintenance/verify-compliance.sh +3 -3
  160. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  161. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  162. package/src/policies/enforcers/adr_guard.py +0 -0
  163. package/src/policies/enforcers/bearer_lockdown.py +0 -0
  164. package/src/policies/enforcers/branch_freshness.py +0 -0
  165. package/src/policies/enforcers/design_token_gate.py +0 -0
  166. package/src/policies/enforcers/enforcement_self_protect.py +70 -0
  167. package/src/policies/enforcers/expert_review_required.py +22 -0
  168. package/src/policies/enforcers/iac_plan_destruction_check.py +0 -0
  169. package/src/policies/enforcers/quality_metrics_gate.py +447 -0
  170. package/src/policies/enforcers/ship_loop_gate.py +0 -0
  171. package/src/policies/enforcers/validate_plan_on_change.py +0 -0
  172. package/src/policies/enforcers/visual_verification.py +0 -0
  173. package/src/policies/enforcers/workdir_scope.py +0 -0
  174. package/src/policies/schemas/policies/expert-review-required.md +9 -1
  175. package/src/policies/schemas/policies/quality-metrics-gate.md +74 -0
  176. package/templates/CLAUDE.template.md +1 -1
  177. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  178. package/templates/hooks/pre-tool-use-bash.sh +50 -3
  179. package/templates/hooks/pre-tool-use-edit-write.sh +31 -0
  180. package/templates/hooks/session-start.sh +15 -0
  181. package/templates/hooks/uap-policy-gate.sh +187 -5
  182. package/templates/stryker.config.json +14 -0
  183. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  184. package/tools/agents/scripts/anthropic_proxy.py +254 -28
  185. package/tools/agents/tests/run_acceptance.py +0 -0
  186. package/tools/agents/tests/test_client_disconnect.py +0 -0
  187. package/tools/agents/tests/test_cycle_break_exploration.py +0 -0
  188. package/tools/agents/tests/test_deferral_break.py +0 -0
  189. package/tools/agents/tests/test_delivery_enforcement_all_langs.py +0 -0
  190. package/tools/agents/tests/test_delivery_enforcement_exemptions.py +0 -0
  191. package/tools/agents/tests/test_delivery_enforcement_filepath.py +0 -0
  192. package/tools/agents/tests/test_delivery_enforcement_web_and_bash.py +0 -0
  193. package/tools/agents/tests/test_delivery_enforcement_worktree.py +0 -0
  194. package/tools/agents/tests/test_delivery_enforcement_write_complexity.py +0 -0
  195. package/tools/agents/tests/test_disconnect_watcher.py +0 -0
  196. package/tools/agents/tests/test_doubling_break.py +0 -0
  197. package/tools/agents/tests/test_empty_maxtokens_recovery.py +0 -0
  198. package/tools/agents/tests/test_empty_tool_loop_break.py +0 -0
  199. package/tools/agents/tests/test_enforcement_self_protect.py +90 -0
  200. package/tools/agents/tests/test_enforcer_escape_hatches.py +0 -0
  201. package/tools/agents/tests/test_error_loop_break.py +0 -0
  202. package/tools/agents/tests/test_expert_review_pr_scope.py +13 -5
  203. package/tools/agents/tests/test_fastpath_config.py +0 -0
  204. package/tools/agents/tests/test_finalize_suppression.py +0 -0
  205. package/tools/agents/tests/test_gate_evidence.py +0 -0
  206. package/tools/agents/tests/test_gate_failclosed_indirection.py +0 -0
  207. package/tools/agents/tests/test_gate_integrity.py +0 -0
  208. package/tools/agents/tests/test_live_deliver_kill.py +0 -0
  209. package/tools/agents/tests/test_malformed_unclosed_think.py +0 -0
  210. package/tools/agents/tests/test_mandate_beats_recon.py +0 -0
  211. package/tools/agents/tests/test_mandate_deliver.py +0 -0
  212. package/tools/agents/tests/test_non_stream_resp_log.py +0 -0
  213. package/tools/agents/tests/test_output_token_ceilings.py +0 -0
  214. package/tools/agents/tests/test_overflow_truncate_count_tokens.py +0 -0
  215. package/tools/agents/tests/test_path_containment.py +0 -0
  216. package/tools/agents/tests/test_path_normalizer_hardened.py +0 -0
  217. package/tools/agents/tests/test_repeat_call_guard.py +0 -0
  218. package/tools/agents/tests/test_repeat_guard.py +0 -0
  219. package/tools/agents/tests/test_session_admission.py +0 -0
  220. package/tools/agents/tests/test_stream_heartbeat.py +0 -0
  221. package/tools/agents/tests/test_stream_thinking_deltas.py +405 -0
  222. package/tools/agents/tests/test_stuck_break_hard.py +0 -0
  223. package/tools/agents/tests/test_stuck_break_reattach.py +0 -0
  224. package/tools/agents/tests/test_turn_count_breaker_periodic.py +0 -0
  225. package/tools/agents/tests/test_upstream_chokepoint.py +0 -0
  226. package/tools/agents/tests/test_upstream_model_aliases.py +0 -0
  227. package/tools/agents/tests/test_validate_plan_gate.py +0 -0
  228. package/tools/agents/tests/test_vision_passthrough.py +0 -0
  229. package/tools/agents/tests/test_workdir_scope_enforcer.py +0 -0
  230. package/dist/cli/rtk.d.ts +0 -34
  231. package/dist/cli/rtk.d.ts.map +0 -1
  232. package/dist/cli/rtk.js +0 -401
  233. package/dist/cli/rtk.js.map +0 -1
  234. package/docs/integrations/RTK.md +0 -113
  235. package/scripts/setup/install-rtk.sh +0 -184
  236. package/src/policies/enforcers/rtk_wrap.py +0 -205
  237. package/src/policies/schemas/policies/rtk-wrap.md +0 -50
  238. package/tools/agents/tests/test_rtk_wrap.py +0 -151
File without changes
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env python3
2
+ """PROXY_STREAM_THINKING_DELTAS: stream reasoning_content as thinking blocks.
3
+
4
+ Regression cover for the 2026-09-05 retry livelock residue: Qwen3.8-27B on
5
+ exllamav3 reasons for 4+ minutes on some tool turns. The relay collected
6
+ `reasoning_content` but never yielded it, so the client (Factory Droid, ~300 s
7
+ stream watchdog) saw `content_block_start` then total silence, disconnected,
8
+ and retried — each retry regenerating the turn from scratch. With the flag on,
9
+ leading reasoning streams as Anthropic `thinking` blocks so the client sees
10
+ continuous activity.
11
+
12
+ The load-bearing half is block discipline: Anthropic SSE requires blocks to
13
+ open/close in index order with no deltas addressed to an unopened block, in
14
+ every layout (thinking->text, thinking->tool, text-only, reasoning-only).
15
+ """
16
+
17
+ import asyncio
18
+ import importlib.util
19
+ import json
20
+ import unittest
21
+ from pathlib import Path
22
+ from unittest import mock
23
+
24
+
25
+ def _load_proxy_module():
26
+ proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
27
+ spec = importlib.util.spec_from_file_location("anthropic_proxy_think", proxy_path)
28
+ assert spec is not None and spec.loader is not None
29
+ module = importlib.util.module_from_spec(spec)
30
+ spec.loader.exec_module(module)
31
+ return module
32
+
33
+
34
+ proxy = _load_proxy_module()
35
+
36
+
37
+ class _FakeUpstreamStream:
38
+ """Minimal streamed httpx.Response: yields pre-built OpenAI SSE deltas."""
39
+
40
+ def __init__(self, deltas, finish_reason="stop"):
41
+ self._deltas = list(deltas)
42
+ self._finish_reason = finish_reason
43
+ self.closed = False
44
+
45
+ async def aiter_lines(self):
46
+ for delta in self._deltas:
47
+ yield "data: " + json.dumps({"choices": [{"delta": delta, "index": 0}]})
48
+ yield "data: " + json.dumps(
49
+ {"choices": [{"delta": {}, "finish_reason": self._finish_reason}]}
50
+ )
51
+ yield "data: [DONE]"
52
+
53
+ async def aclose(self):
54
+ self.closed = True
55
+
56
+
57
+ class _SlowUpstreamStream(_FakeUpstreamStream):
58
+ """_FakeUpstreamStream with real async delays before each delta, so the
59
+ relay's prefill-heartbeat wait (asyncio.wait timeout) actually elapses."""
60
+
61
+ def __init__(self, deltas, finish_reason="stop", delay=0.2):
62
+ super().__init__(deltas, finish_reason)
63
+ self._delay = delay
64
+
65
+ async def aiter_lines(self):
66
+ for delta in self._deltas:
67
+ await asyncio.sleep(self._delay)
68
+ yield "data: " + json.dumps({"choices": [{"delta": delta, "index": 0}]})
69
+ yield "data: " + json.dumps(
70
+ {"choices": [{"delta": {}, "finish_reason": self._finish_reason}]}
71
+ )
72
+ yield "data: [DONE]"
73
+
74
+
75
+ def _drain(upstream, stream_thinking=True, heartbeat_secs=0.0):
76
+ monitor = proxy.SessionMonitor(context_window=131072)
77
+ body = {
78
+ "messages": [{"role": "user", "content": "go"}],
79
+ "tools": [
80
+ {"name": "Bash", "description": "run", "input_schema": {"type": "object"}}
81
+ ],
82
+ }
83
+
84
+ async def run():
85
+ out = []
86
+ with mock.patch.object(
87
+ proxy, "PROXY_STREAM_THINKING_DELTAS", stream_thinking
88
+ ), mock.patch.object(
89
+ proxy, "PROXY_PREFILL_HEARTBEAT_SECS", heartbeat_secs
90
+ ):
91
+ async for frame in proxy.stream_anthropic_response(
92
+ upstream, "test-model", monitor, body
93
+ ):
94
+ out.append(frame)
95
+ return out
96
+
97
+ return asyncio.run(run())
98
+
99
+
100
+ def _events(frames):
101
+ """Parse SSE frames into a flat list of Anthropic event dicts."""
102
+ out = []
103
+ for frame in frames:
104
+ for line in frame.splitlines():
105
+ if not line.startswith("data: "):
106
+ continue
107
+ try:
108
+ out.append(json.loads(line[6:]))
109
+ except json.JSONDecodeError:
110
+ continue
111
+ return out
112
+
113
+
114
+ def _assert_block_discipline(testcase, events):
115
+ """Every opened block closes exactly once, in order, and no delta or stop
116
+ addresses a block that is not open."""
117
+ expected_next = 0
118
+ open_index = None
119
+ for ev in events:
120
+ etype = ev.get("type")
121
+ if etype == "content_block_start":
122
+ testcase.assertIsNone(open_index, f"block {ev['index']} opened while {open_index} still open")
123
+ testcase.assertEqual(ev["index"], expected_next, "blocks must open in index order")
124
+ open_index = ev["index"]
125
+ expected_next += 1
126
+ elif etype == "content_block_delta":
127
+ testcase.assertIsNotNone(open_index, "delta with no open block")
128
+ testcase.assertEqual(ev["index"], open_index, "delta addressed to a non-open block")
129
+ elif etype == "content_block_stop":
130
+ testcase.assertEqual(ev["index"], open_index, "stop addressed to a non-open block")
131
+ open_index = None
132
+ elif etype == "message_stop":
133
+ testcase.assertIsNone(open_index, f"block {open_index} never closed")
134
+ return events
135
+
136
+
137
+ _DELTA_PAYLOAD_KEY = {
138
+ "text_delta": "text",
139
+ "thinking_delta": "thinking",
140
+ "input_json_delta": "partial_json",
141
+ }
142
+
143
+
144
+ def _deltas_of(events, delta_type):
145
+ key = _DELTA_PAYLOAD_KEY[delta_type]
146
+ return [
147
+ ev["delta"][key]
148
+ for ev in events
149
+ if ev.get("type") == "content_block_delta"
150
+ and ev.get("delta", {}).get("type") == delta_type
151
+ ]
152
+
153
+
154
+ def _block_kinds(events):
155
+ return [
156
+ ev["content_block"]["type"]
157
+ for ev in events
158
+ if ev.get("type") == "content_block_start"
159
+ ]
160
+
161
+
162
+ class TestStreamThinkingDeltas(unittest.TestCase):
163
+ def test_leading_reasoning_streams_as_thinking_block_then_text(self):
164
+ upstream = _FakeUpstreamStream(
165
+ [
166
+ {"reasoning_content": "Let me check "},
167
+ {"reasoning_content": "the workspace first."},
168
+ {"content": "Looking at the workspace."},
169
+ ]
170
+ )
171
+ events = _assert_block_discipline(self, _events(_drain(upstream)))
172
+
173
+ self.assertEqual(_block_kinds(events), ["thinking", "text"])
174
+ self.assertEqual(
175
+ _deltas_of(events, "thinking_delta"),
176
+ ["Let me check ", "the workspace first."],
177
+ )
178
+ self.assertEqual(
179
+ _deltas_of(events, "text_delta"), ["Looking at the workspace."]
180
+ )
181
+ # Thinking block must be index 0, text block index 1.
182
+ starts = [ev for ev in events if ev.get("type") == "content_block_start"]
183
+ self.assertEqual(
184
+ [(s["index"], s["content_block"]["type"]) for s in starts],
185
+ [(0, "thinking"), (1, "text")],
186
+ )
187
+
188
+ def test_reasoning_then_tool_call_closes_thinking_first(self):
189
+ upstream = _FakeUpstreamStream(
190
+ [
191
+ {"reasoning_content": "I should run pwd."},
192
+ {
193
+ "tool_calls": [
194
+ {
195
+ "index": 0,
196
+ "id": "toolu_abc",
197
+ "function": {"name": "Bash", "arguments": '{"command":'},
198
+ }
199
+ ]
200
+ },
201
+ {
202
+ "tool_calls": [
203
+ {"index": 0, "function": {"arguments": ' "pwd"}'}}
204
+ ]
205
+ },
206
+ ],
207
+ finish_reason="tool_calls",
208
+ )
209
+ events = _assert_block_discipline(self, _events(_drain(upstream)))
210
+
211
+ self.assertEqual(_block_kinds(events), ["thinking", "tool_use"])
212
+ self.assertEqual(
213
+ _deltas_of(events, "thinking_delta"), ["I should run pwd."]
214
+ )
215
+ args = "".join(_deltas_of(events, "input_json_delta"))
216
+ self.assertEqual(json.loads(args), {"command": "pwd"})
217
+ stops = [ev for ev in events if ev.get("type") == "message_delta"]
218
+ self.assertEqual(stops[-1]["delta"]["stop_reason"], "tool_use")
219
+
220
+ def test_flag_off_keeps_legacy_layout_and_hides_reasoning(self):
221
+ upstream = _FakeUpstreamStream(
222
+ [
223
+ {"reasoning_content": "secret chain of thought"},
224
+ {"content": "Visible answer."},
225
+ ]
226
+ )
227
+ events = _assert_block_discipline(
228
+ self, _events(_drain(upstream, stream_thinking=False))
229
+ )
230
+
231
+ self.assertEqual(_block_kinds(events), ["text"])
232
+ self.assertEqual(_deltas_of(events, "thinking_delta"), [])
233
+ self.assertEqual(_deltas_of(events, "text_delta"), ["Visible answer."])
234
+ starts = [ev for ev in events if ev.get("type") == "content_block_start"]
235
+ self.assertEqual(starts[0]["index"], 0)
236
+
237
+ def test_text_only_turn_keeps_simple_layout_under_flag(self):
238
+ upstream = _FakeUpstreamStream([{"content": "Just text."}])
239
+ events = _assert_block_discipline(self, _events(_drain(upstream)))
240
+
241
+ self.assertEqual(_block_kinds(events), ["text"])
242
+ self.assertEqual(_deltas_of(events, "text_delta"), ["Just text."])
243
+ starts = [ev for ev in events if ev.get("type") == "content_block_start"]
244
+ self.assertEqual(starts[0]["index"], 0)
245
+
246
+ def test_reasoning_only_turn_closes_thinking_block(self):
247
+ """A turn that is all reasoning must still end well-formed: the
248
+ thinking block closes (and the reasoning-fallback contract decides
249
+ what text, if any, follows) before message_stop."""
250
+ upstream = _FakeUpstreamStream([{"reasoning_content": "hmm"}])
251
+ events = _assert_block_discipline(self, _events(_drain(upstream)))
252
+
253
+ self.assertEqual(_block_kinds(events)[0], "thinking")
254
+ self.assertEqual(_deltas_of(events, "thinking_delta"), ["hmm"])
255
+ # Well-formed stream ends with message_delta then message_stop.
256
+ self.assertEqual(events[-2]["type"], "message_delta")
257
+ self.assertEqual(events[-1]["type"], "message_stop")
258
+
259
+
260
+ class TestPrefillHeartbeat(unittest.TestCase):
261
+ """PROXY_PREFILL_HEARTBEAT_SECS: one-space thinking_deltas while the
262
+ upstream stream is silent in the leading phase (prompt prefill).
263
+
264
+ Regression cover for the 2026-09-09 livelock: Qwen3.8-Flash-Next on a
265
+ single RTX 3090 prefills a 64k-token session turn in 170-400 s, past the
266
+ Factory Droid ~242 s stream watchdog. message_start and ping events do
267
+ not reset that watchdog; each kill landed mid-prefill and the retry
268
+ re-prefilled from token 0. The heartbeat keeps content deltas flowing
269
+ without changing visible output.
270
+ """
271
+
272
+ def test_heartbeat_streams_thinking_spaces_before_text(self):
273
+ upstream = _SlowUpstreamStream([{"content": "answer"}], delay=0.22)
274
+ events = _assert_block_discipline(
275
+ self, _events(_drain(upstream, heartbeat_secs=0.05))
276
+ )
277
+
278
+ self.assertEqual(_block_kinds(events), ["thinking", "text"])
279
+ thinking = _deltas_of(events, "thinking_delta")
280
+ self.assertGreaterEqual(len(thinking), 2)
281
+ self.assertTrue(all(t == " " for t in thinking))
282
+ self.assertEqual(_deltas_of(events, "text_delta"), ["answer"])
283
+
284
+ def test_heartbeat_shares_block_with_real_reasoning(self):
285
+ upstream = _SlowUpstreamStream(
286
+ [{"reasoning_content": "real thought"}, {"content": "answer"}],
287
+ delay=0.12,
288
+ )
289
+ events = _assert_block_discipline(
290
+ self, _events(_drain(upstream, heartbeat_secs=0.05))
291
+ )
292
+
293
+ self.assertEqual(_block_kinds(events), ["thinking", "text"])
294
+ thinking = _deltas_of(events, "thinking_delta")
295
+ # Heartbeat spaces and the real reasoning share ONE thinking block.
296
+ self.assertEqual(thinking.count("real thought"), 1)
297
+ self.assertGreaterEqual(len(thinking), 3)
298
+ self.assertTrue(all(t in (" ", "real thought") for t in thinking))
299
+
300
+ def test_heartbeat_off_by_default_keeps_plain_text_layout(self):
301
+ upstream = _SlowUpstreamStream([{"content": "answer"}], delay=0.2)
302
+ events = _assert_block_discipline(self, _events(_drain(upstream)))
303
+
304
+ self.assertEqual(_block_kinds(events), ["text"])
305
+ self.assertEqual(_deltas_of(events, "thinking_delta"), [])
306
+ self.assertEqual(_deltas_of(events, "text_delta"), ["answer"])
307
+
308
+
309
+ def _drain_guarded(produced, delay=0.2, prefill_hb_secs=0.05):
310
+ """Drive _heartbeat_then_buffered with a produce coroutine that resolves
311
+ after `delay` seconds; ping/timer interval shrunk to match."""
312
+
313
+ async def run():
314
+ async def produce():
315
+ await asyncio.sleep(delay)
316
+ return produced
317
+
318
+ out = []
319
+ with mock.patch.object(
320
+ proxy, "PROXY_STREAM_THINKING_DELTAS", True
321
+ ), mock.patch.object(
322
+ proxy, "PROXY_PREFILL_HEARTBEAT_SECS", prefill_hb_secs
323
+ ), mock.patch.object(
324
+ proxy, "PROXY_STREAM_HEARTBEAT_SECS", 0.05
325
+ ):
326
+ async for frame in proxy._heartbeat_then_buffered(
327
+ produce(), "test-model", 100
328
+ ):
329
+ out.append(frame)
330
+ return out
331
+
332
+ return asyncio.run(run())
333
+
334
+
335
+ def _text_response(text):
336
+ return {
337
+ "id": "msg_test",
338
+ "type": "message",
339
+ "role": "assistant",
340
+ "model": "test-model",
341
+ "content": [{"type": "text", "text": text}],
342
+ "stop_reason": "end_turn",
343
+ "usage": {"input_tokens": 100, "output_tokens": 5},
344
+ }
345
+
346
+
347
+ class TestGuardedPathHeartbeat(unittest.TestCase):
348
+ """The guarded non-stream path buffers the whole upstream generation; its
349
+ ping-only keep-alive did not reset the Droid ~242 s stream watchdog, so
350
+ long no-tool turns (e.g. session summarization, ~470 s total) livelocked
351
+ on 2026-09-09. With PROXY_PREFILL_HEARTBEAT_SECS on, the keep-alive is a
352
+ thinking block of one-space deltas and the buffered content re-indexes +1.
353
+ """
354
+
355
+ def test_guarded_heartbeat_thinking_then_text_reindexed(self):
356
+ events = _assert_block_discipline(
357
+ self, _events(_drain_guarded(_text_response("summary"), delay=0.22))
358
+ )
359
+
360
+ self.assertEqual(_block_kinds(events), ["thinking", "text"])
361
+ thinking = _deltas_of(events, "thinking_delta")
362
+ self.assertGreaterEqual(len(thinking), 2)
363
+ self.assertTrue(all(t == " " for t in thinking))
364
+ self.assertEqual(_deltas_of(events, "text_delta"), ["summary"])
365
+ starts = [ev for ev in events if ev.get("type") == "content_block_start"]
366
+ self.assertEqual(
367
+ [(s["index"], s["content_block"]["type"]) for s in starts],
368
+ [(0, "thinking"), (1, "text")],
369
+ )
370
+
371
+ def test_guarded_heartbeat_off_keeps_pings_and_text_at_zero(self):
372
+ events = _assert_block_discipline(
373
+ self,
374
+ _events(_drain_guarded(_text_response("summary"), prefill_hb_secs=0.0)),
375
+ )
376
+
377
+ self.assertEqual(_block_kinds(events), ["text"])
378
+ self.assertEqual(_deltas_of(events, "thinking_delta"), [])
379
+ starts = [ev for ev in events if ev.get("type") == "content_block_start"]
380
+ self.assertEqual(starts[0]["index"], 0)
381
+ pings = [ev for ev in events if ev.get("type") == "ping"]
382
+ self.assertGreaterEqual(len(pings), 2)
383
+
384
+ def test_guarded_heartbeat_error_response_closes_thinking_block(self):
385
+ err = proxy.Response(
386
+ content=json.dumps(
387
+ {"error": {"code": 500, "message": "boom", "type": "server_error"}}
388
+ ),
389
+ status_code=500,
390
+ media_type="application/json",
391
+ )
392
+ frames = _drain_guarded(err, delay=0.22)
393
+ events = _assert_block_discipline(self, _events(frames))
394
+
395
+ self.assertEqual(_block_kinds(events), ["thinking"])
396
+ # Thinking block closed before the terminal SSE error frame.
397
+ stop_at = next(
398
+ i for i, f in enumerate(frames) if "content_block_stop" in f
399
+ )
400
+ error_at = next(i for i, f in enumerate(frames) if "event: error" in f)
401
+ self.assertLess(stop_at, error_at)
402
+
403
+
404
+ if __name__ == "__main__":
405
+ unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/cli/rtk.d.ts DELETED
@@ -1,34 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * RTK (Rust Token Killer) Integration for UAP
4
- *
5
- * Provides commands to install and manage RTK CLI proxy
6
- * for 60-90% token savings on command outputs.
7
- */
8
- export interface RTKInstallOptions {
9
- force?: boolean;
10
- method?: 'homebrew' | 'cargo' | 'curl';
11
- }
12
- /**
13
- * Install RTK with auto-detection of best method
14
- */
15
- export declare function installRTK(options?: RTKInstallOptions): Promise<void>;
16
- /**
17
- * Check RTK status
18
- */
19
- export declare function checkRTKStatus(): Promise<void>;
20
- /**
21
- * Run an arbitrary RTK command by passing through all args to the rtk binary.
22
- * If RTK is not installed, prints install instructions.
23
- */
24
- export declare function runRTKCommand(args: string[]): void;
25
- /**
26
- * Show unified analytics combining MCP Router stats and RTK stats.
27
- * Reads MCP Router config from .uap-mcp-router.json and RTK stats from `rtk gain`.
28
- */
29
- export declare function showUnifiedAnalytics(): void;
30
- /**
31
- * Show RTK help
32
- */
33
- export declare function showRTKHelp(): void;
34
- //# sourceMappingURL=rtk.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rtk.d.ts","sourceRoot":"","sources":["../../src/cli/rtk.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAMH,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;CACxC;AA+ID;;GAEG;AACH,wBAAsB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyF/E;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA4CpD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAkBlD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CA+D3C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAqClC"}