@miller-tech/uap 1.133.2 → 1.135.1
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/bin/cli.js +4 -1
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/config-command.d.ts +49 -0
- package/dist/cli/config-command.d.ts.map +1 -0
- package/dist/cli/config-command.js +465 -0
- package/dist/cli/config-command.js.map +1 -0
- package/dist/cli/config-wizard.d.ts +13 -0
- package/dist/cli/config-wizard.d.ts.map +1 -0
- package/dist/cli/config-wizard.js +134 -0
- package/dist/cli/config-wizard.js.map +1 -0
- package/dist/cli/deliver.js +11 -2
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/guided-setup.d.ts.map +1 -1
- package/dist/cli/guided-setup.js +11 -0
- package/dist/cli/guided-setup.js.map +1 -1
- package/dist/cli/policy.d.ts.map +1 -1
- package/dist/cli/policy.js +34 -0
- package/dist/cli/policy.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +22 -0
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/policy-recommendations.d.ts +29 -0
- package/dist/config/policy-recommendations.d.ts.map +1 -0
- package/dist/config/policy-recommendations.js +108 -0
- package/dist/config/policy-recommendations.js.map +1 -0
- package/dist/config/settings-registry.d.ts +65 -0
- package/dist/config/settings-registry.d.ts.map +1 -0
- package/dist/config/settings-registry.js +334 -0
- package/dist/config/settings-registry.js.map +1 -0
- package/dist/dashboard/controls.d.ts +66 -0
- package/dist/dashboard/controls.d.ts.map +1 -0
- package/dist/dashboard/controls.js +188 -0
- package/dist/dashboard/controls.js.map +1 -0
- package/dist/dashboard/data-service.d.ts +20 -0
- package/dist/dashboard/data-service.d.ts.map +1 -1
- package/dist/dashboard/data-service.js +47 -0
- package/dist/dashboard/data-service.js.map +1 -1
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +81 -5
- package/dist/dashboard/server.js.map +1 -1
- package/dist/delivery/convergence-loop.d.ts +5 -0
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +11 -0
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/run-state.d.ts +12 -0
- package/dist/delivery/run-state.d.ts.map +1 -1
- package/dist/delivery/run-state.js +58 -1
- package/dist/delivery/run-state.js.map +1 -1
- package/docs/DASHBOARD_UPLIFT_SPEC.md +171 -0
- package/docs/INDEX.md +2 -0
- package/docs/guides/POLICY_SELECTION.md +98 -0
- package/docs/reference/CONFIGURATION_REFERENCE.md +688 -0
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +164 -12
- package/web/dash/core.js +398 -0
- package/web/dash/styles.css +307 -0
- package/web/dash/tab-deliver.js +12 -0
- package/web/dash/tab-memory.js +12 -0
- package/web/dash/tab-models.js +12 -0
- package/web/dash/tab-policies.js +12 -0
- package/web/dash/tabs.js +581 -0
- package/web/dashboard.html +12 -1303
|
@@ -184,6 +184,27 @@ PROXY_CONTEXT_WINDOW = int(os.environ.get("PROXY_CONTEXT_WINDOW", "0"))
|
|
|
184
184
|
PROXY_CONTEXT_PRUNE_THRESHOLD = float(
|
|
185
185
|
os.environ.get("PROXY_CONTEXT_PRUNE_THRESHOLD", "0.85")
|
|
186
186
|
)
|
|
187
|
+
# Compaction forcing (Option A, 2026-07-10): Claude Code decides when to
|
|
188
|
+
# auto-compact against ITS believed model window (~200k) using
|
|
189
|
+
# /v1/messages/count_tokens, so an HONEST count on a smaller local rail lets
|
|
190
|
+
# every session grow into the "thrash band" — above the rail (per-request
|
|
191
|
+
# critical prunes, findings evaporate, read-only doom loops; observed live:
|
|
192
|
+
# 87 reads / 0 writes in 30 min at 122% utilization) but below the client's
|
|
193
|
+
# compact trigger (~92.5% of 200k). Scaling the reported count makes the
|
|
194
|
+
# client compact BEFORE the rail. "auto" (default) derives the scale from the
|
|
195
|
+
# LIVE rail at call time — assumed_window / (rail * target_fraction) — so it
|
|
196
|
+
# tracks rail resizes; a number forces that scale; "1"/"off" disables.
|
|
197
|
+
PROXY_COUNT_TOKENS_SCALE = os.environ.get("PROXY_COUNT_TOKENS_SCALE", "auto")
|
|
198
|
+
PROXY_CLIENT_ASSUMED_WINDOW = int(
|
|
199
|
+
os.environ.get("PROXY_CLIENT_ASSUMED_WINDOW", "200000")
|
|
200
|
+
)
|
|
201
|
+
# Where compaction should land sessions, as a fraction of the rail. Unset
|
|
202
|
+
# (0) = auto: just under the pruner threshold (x0.95), so the client's own
|
|
203
|
+
# compaction fires BEFORE the proxy ever needs to prune — the pruner stays on
|
|
204
|
+
# purely as a backstop. An explicit value overrides (clamped to <1).
|
|
205
|
+
PROXY_COMPACT_TARGET_FRACTION = float(
|
|
206
|
+
os.environ.get("PROXY_COMPACT_TARGET_FRACTION", "0")
|
|
207
|
+
)
|
|
187
208
|
PROXY_CONTEXT_PRUNE_TARGET_FRACTION = float(
|
|
188
209
|
os.environ.get("PROXY_CONTEXT_PRUNE_TARGET_FRACTION", "0.50")
|
|
189
210
|
)
|
|
@@ -1329,6 +1350,7 @@ class SessionMonitor:
|
|
|
1329
1350
|
deferral_streak: int = 0 # consecutive no-tool turns deferring the work (Fix A)
|
|
1330
1351
|
deferral_break_fires: int = 0 # monotonic count of forced deferral-breaks (Fix A)
|
|
1331
1352
|
tool_starvation_streak: int = 0 # Consecutive forced turns with no tool_calls produced
|
|
1353
|
+
last_request_msg_count: int = 0 # Message count of the previous request (compaction-boundary detection)
|
|
1332
1354
|
malformed_tool_streak: int = 0 # consecutive malformed pseudo tool payloads
|
|
1333
1355
|
invalid_tool_call_streak: int = 0 # consecutive invalid tool arg payloads
|
|
1334
1356
|
required_tool_miss_streak: int = 0 # required tool turns with no tool call
|
|
@@ -8936,7 +8958,7 @@ async def _maybe_apply_recipe(anthropic_resp, anthropic_body, openai_body, clien
|
|
|
8936
8958
|
return anthropic_resp
|
|
8937
8959
|
|
|
8938
8960
|
|
|
8939
|
-
async def _heartbeat_then_buffered(produce_coro, model: str):
|
|
8961
|
+
async def _heartbeat_then_buffered(produce_coro, model: str, input_tokens: int = 0):
|
|
8940
8962
|
"""SSE generator: keep-alive heartbeat wrapper for the guarded-non-stream path.
|
|
8941
8963
|
|
|
8942
8964
|
Emits an immediate ``message_start`` so the client registers an active
|
|
@@ -8953,7 +8975,7 @@ async def _heartbeat_then_buffered(produce_coro, model: str):
|
|
|
8953
8975
|
msg_id = f"msg_{uuid.uuid4().hex[:24]}"
|
|
8954
8976
|
yield (
|
|
8955
8977
|
f"event: message_start\n"
|
|
8956
|
-
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens':
|
|
8978
|
+
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens': input_tokens, 'output_tokens': 0}}})}\n\n"
|
|
8957
8979
|
)
|
|
8958
8980
|
interval = PROXY_STREAM_HEARTBEAT_SECS if PROXY_STREAM_HEARTBEAT_SECS > 0 else 15.0
|
|
8959
8981
|
task = asyncio.ensure_future(produce_coro)
|
|
@@ -9011,7 +9033,13 @@ async def stream_anthropic_message(anthropic_resp: dict, emit_message_start: boo
|
|
|
9011
9033
|
"model": anthropic_resp.get("model", "unknown"),
|
|
9012
9034
|
"stop_reason": None,
|
|
9013
9035
|
"stop_sequence": None,
|
|
9014
|
-
"usage": {
|
|
9036
|
+
"usage": {
|
|
9037
|
+
"input_tokens": int(
|
|
9038
|
+
(anthropic_resp.get("usage", {}) or {}).get("input_tokens", 0)
|
|
9039
|
+
* _count_tokens_scale()
|
|
9040
|
+
),
|
|
9041
|
+
"output_tokens": 0,
|
|
9042
|
+
},
|
|
9015
9043
|
}
|
|
9016
9044
|
yield f"event: message_start\ndata: {json.dumps({'type': 'message_start', 'message': message})}\n\n"
|
|
9017
9045
|
|
|
@@ -9053,11 +9081,13 @@ async def stream_anthropic_message(anthropic_resp: dict, emit_message_start: boo
|
|
|
9053
9081
|
)
|
|
9054
9082
|
block_index += 1
|
|
9055
9083
|
|
|
9056
|
-
|
|
9084
|
+
resp_usage = anthropic_resp.get("usage", {}) or {}
|
|
9085
|
+
output_tokens = resp_usage.get("output_tokens", 0)
|
|
9086
|
+
scaled_input = int(resp_usage.get("input_tokens", 0) * _count_tokens_scale())
|
|
9057
9087
|
stop_reason = anthropic_resp.get("stop_reason", "end_turn")
|
|
9058
9088
|
yield (
|
|
9059
9089
|
"event: message_delta\n"
|
|
9060
|
-
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': stop_reason, 'stop_sequence': None}, 'usage': {'output_tokens': output_tokens}})}\n\n"
|
|
9090
|
+
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': stop_reason, 'stop_sequence': None}, 'usage': {'input_tokens': scaled_input, 'output_tokens': output_tokens}})}\n\n"
|
|
9061
9091
|
)
|
|
9062
9092
|
yield f"event: message_stop\ndata: {json.dumps({'type': 'message_stop'})}\n\n"
|
|
9063
9093
|
|
|
@@ -9084,10 +9114,11 @@ async def stream_anthropic_response(
|
|
|
9084
9114
|
msg_id = f"msg_{uuid.uuid4().hex[:24]}"
|
|
9085
9115
|
stream_started = time.monotonic()
|
|
9086
9116
|
|
|
9087
|
-
# message_start
|
|
9117
|
+
# message_start — carries the client's (scaled) input size; a hardcoded 0
|
|
9118
|
+
# here blinded clients to their own context usage (compaction never fired).
|
|
9088
9119
|
yield (
|
|
9089
9120
|
f"event: message_start\n"
|
|
9090
|
-
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens':
|
|
9121
|
+
f"data: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'content': [], 'model': model, 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens': _client_input_tokens(monitor), 'output_tokens': 0}}})}\n\n"
|
|
9091
9122
|
)
|
|
9092
9123
|
|
|
9093
9124
|
# content_block_start for text (index 0)
|
|
@@ -9392,10 +9423,12 @@ async def stream_anthropic_response(
|
|
|
9392
9423
|
if _is_unexpected_end_turn(synthetic_openai_resp, anthropic_body):
|
|
9393
9424
|
monitor.unexpected_end_turn_count += 1
|
|
9394
9425
|
|
|
9395
|
-
# message_delta with final stop reason
|
|
9426
|
+
# message_delta with final stop reason. Repeats the (scaled) input size so
|
|
9427
|
+
# clients that read usage from the delta rather than message_start still
|
|
9428
|
+
# see their context usage — the compaction-forcing signal.
|
|
9396
9429
|
yield (
|
|
9397
9430
|
f"event: message_delta\n"
|
|
9398
|
-
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': finish_reason, 'stop_sequence': None}, 'usage': {'output_tokens': output_tokens}})}\n\n"
|
|
9431
|
+
f"data: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': finish_reason, 'stop_sequence': None}, 'usage': {'input_tokens': _client_input_tokens(monitor), 'output_tokens': output_tokens}})}\n\n"
|
|
9399
9432
|
)
|
|
9400
9433
|
|
|
9401
9434
|
# message_stop
|
|
@@ -9563,7 +9596,98 @@ async def count_tokens(request: Request):
|
|
|
9563
9596
|
status_code=400,
|
|
9564
9597
|
media_type="application/json",
|
|
9565
9598
|
)
|
|
9566
|
-
|
|
9599
|
+
est = estimate_total_tokens(body)
|
|
9600
|
+
scale = _count_tokens_scale()
|
|
9601
|
+
if scale > 1.0:
|
|
9602
|
+
scaled = int(est * scale)
|
|
9603
|
+
# Once per scale value, explain the discrepancy an operator would
|
|
9604
|
+
# otherwise chase between this endpoint and the pruner's numbers.
|
|
9605
|
+
global _count_scale_logged
|
|
9606
|
+
if _count_scale_logged != scale:
|
|
9607
|
+
_count_scale_logged = scale
|
|
9608
|
+
logger.info(
|
|
9609
|
+
"COMPACT FORCING: scaling count_tokens by %.2f "
|
|
9610
|
+
"(client assumed window %d, rail %d) — client auto-compact "
|
|
9611
|
+
"fires at ~%d real tokens, before the pruner",
|
|
9612
|
+
scale,
|
|
9613
|
+
PROXY_CLIENT_ASSUMED_WINDOW,
|
|
9614
|
+
default_context_window if default_context_window > 0 else PROXY_CONTEXT_WINDOW,
|
|
9615
|
+
int(PROXY_CLIENT_ASSUMED_WINDOW * 0.925 / scale),
|
|
9616
|
+
)
|
|
9617
|
+
return {"input_tokens": scaled}
|
|
9618
|
+
return {"input_tokens": est}
|
|
9619
|
+
|
|
9620
|
+
|
|
9621
|
+
_count_scale_logged: float = 0.0
|
|
9622
|
+
|
|
9623
|
+
|
|
9624
|
+
def _count_tokens_scale() -> float:
|
|
9625
|
+
"""Resolve the count_tokens compaction-forcing scale (>= 1.0; 1.0 = off).
|
|
9626
|
+
|
|
9627
|
+
"auto" derives it from the LIVE rail each call so a rail resize (server
|
|
9628
|
+
restart with a different --ctx-size) re-tunes the client's compact point
|
|
9629
|
+
without a proxy restart.
|
|
9630
|
+
"""
|
|
9631
|
+
raw = PROXY_COUNT_TOKENS_SCALE.strip().lower()
|
|
9632
|
+
if raw in ("", "off", "none", "0", "1", "1.0"):
|
|
9633
|
+
return 1.0
|
|
9634
|
+
if raw != "auto":
|
|
9635
|
+
try:
|
|
9636
|
+
return max(1.0, float(raw))
|
|
9637
|
+
except ValueError:
|
|
9638
|
+
return 1.0
|
|
9639
|
+
window = default_context_window if default_context_window > 0 else PROXY_CONTEXT_WINDOW
|
|
9640
|
+
if window <= 0:
|
|
9641
|
+
return 1.0
|
|
9642
|
+
frac = (
|
|
9643
|
+
PROXY_COMPACT_TARGET_FRACTION
|
|
9644
|
+
if 0 < PROXY_COMPACT_TARGET_FRACTION < 1
|
|
9645
|
+
# Auto: land compaction just under the pruner trigger so the client
|
|
9646
|
+
# compacts before the proxy ever prunes (pruner = backstop only).
|
|
9647
|
+
else min(0.9, PROXY_CONTEXT_PRUNE_THRESHOLD * 0.95)
|
|
9648
|
+
)
|
|
9649
|
+
target = window * frac
|
|
9650
|
+
if target <= 0 or PROXY_CLIENT_ASSUMED_WINDOW <= target:
|
|
9651
|
+
# Rail already exceeds the client's own compact point — honest counts
|
|
9652
|
+
# are fine, the client compacts before the rail unaided.
|
|
9653
|
+
return 1.0
|
|
9654
|
+
return PROXY_CLIENT_ASSUMED_WINDOW / target
|
|
9655
|
+
|
|
9656
|
+
|
|
9657
|
+
def _client_input_tokens(monitor) -> int:
|
|
9658
|
+
"""Input size to REPORT to the client, scaled for compaction forcing.
|
|
9659
|
+
|
|
9660
|
+
Streaming responses historically hardcoded ``input_tokens: 0`` in
|
|
9661
|
+
message_start — the client could never see its own context usage, so its
|
|
9662
|
+
auto-compaction NEVER fired and sessions ground against the pruner forever
|
|
9663
|
+
(the actual root cause of the interactive thrash band; count_tokens
|
|
9664
|
+
scaling alone was insufficient because the dominant clients don't call
|
|
9665
|
+
it). Reports the PRE-prune estimate — the client's own conversation size,
|
|
9666
|
+
which is what its compaction decision is about — times the forcing scale.
|
|
9667
|
+
"""
|
|
9668
|
+
est = (
|
|
9669
|
+
getattr(monitor, "pre_prune_input_tokens", 0)
|
|
9670
|
+
or getattr(monitor, "last_input_tokens", 0)
|
|
9671
|
+
or 0
|
|
9672
|
+
)
|
|
9673
|
+
return int(est * _count_tokens_scale())
|
|
9674
|
+
|
|
9675
|
+
|
|
9676
|
+
def _scale_client_usage(anthropic_resp: dict) -> dict:
|
|
9677
|
+
"""Shallow-copy a finalized response with usage.input_tokens scaled for the
|
|
9678
|
+
CLIENT. Internal consumers (pruner accounting, telemetry) read the original
|
|
9679
|
+
dict's honest numbers — only the wire copy lies, and it lies consistently
|
|
9680
|
+
with count_tokens and the streaming frames."""
|
|
9681
|
+
scale = _count_tokens_scale()
|
|
9682
|
+
usage = anthropic_resp.get("usage")
|
|
9683
|
+
if scale <= 1.0 or not isinstance(usage, dict):
|
|
9684
|
+
return anthropic_resp
|
|
9685
|
+
out = dict(anthropic_resp)
|
|
9686
|
+
out["usage"] = {
|
|
9687
|
+
**usage,
|
|
9688
|
+
"input_tokens": int(usage.get("input_tokens", 0) * scale),
|
|
9689
|
+
}
|
|
9690
|
+
return out
|
|
9567
9691
|
|
|
9568
9692
|
|
|
9569
9693
|
@app.post("/v1/messages")
|
|
@@ -9692,6 +9816,32 @@ async def messages(request: Request):
|
|
|
9692
9816
|
last_text,
|
|
9693
9817
|
)
|
|
9694
9818
|
|
|
9819
|
+
# --- Compaction-boundary detection: a client-side auto-compact collapses
|
|
9820
|
+
# the conversation (observed live 2026-07-10: msgs 61 -> 3) into a fresh
|
|
9821
|
+
# epoch whose last assistant message is the text-only SUMMARY. Anti-spin
|
|
9822
|
+
# counters accumulated before the boundary (consecutive_forced_count,
|
|
9823
|
+
# starvation/no-write streaks, tool-state machine) then misfire on the
|
|
9824
|
+
# very first post-compact turn — the starvation breaker stripped tools
|
|
9825
|
+
# from a brand-new epoch because the summary "looked like" a text-only
|
|
9826
|
+
# stall. A halving-plus collapse of the message count is the boundary
|
|
9827
|
+
# signal; reset the per-conversation spin state so the new epoch starts
|
|
9828
|
+
# clean. (Token accounting is NOT reset — record_request below re-measures
|
|
9829
|
+
# the compacted size naturally.)
|
|
9830
|
+
prev_msg_count = getattr(monitor, "last_request_msg_count", 0)
|
|
9831
|
+
if prev_msg_count >= 8 and n_messages <= prev_msg_count // 2:
|
|
9832
|
+
monitor.reset_tool_turn_state(reason="compaction_boundary")
|
|
9833
|
+
monitor.consecutive_forced_count = 0
|
|
9834
|
+
monitor.no_progress_streak = 0
|
|
9835
|
+
monitor.tool_starvation_streak = 0
|
|
9836
|
+
monitor.consecutive_no_write_turns = 0
|
|
9837
|
+
logger.info(
|
|
9838
|
+
"COMPACTION BOUNDARY: message count collapsed %d -> %d; reset "
|
|
9839
|
+
"tool-turn/anti-spin state for the fresh epoch",
|
|
9840
|
+
prev_msg_count,
|
|
9841
|
+
n_messages,
|
|
9842
|
+
)
|
|
9843
|
+
monitor.last_request_msg_count = n_messages
|
|
9844
|
+
|
|
9695
9845
|
# --- Option F: Estimate tokens and record in session monitor ---
|
|
9696
9846
|
estimated_tokens = estimate_total_tokens(body)
|
|
9697
9847
|
monitor.record_request(estimated_tokens)
|
|
@@ -10037,7 +10187,7 @@ async def messages(request: Request):
|
|
|
10037
10187
|
|
|
10038
10188
|
if PROXY_STREAM_HEARTBEAT_SECS > 0:
|
|
10039
10189
|
return StreamingResponse(
|
|
10040
|
-
_heartbeat_then_buffered(_produce_guarded(), model),
|
|
10190
|
+
_heartbeat_then_buffered(_produce_guarded(), model, _client_input_tokens(monitor)),
|
|
10041
10191
|
media_type="text/event-stream",
|
|
10042
10192
|
headers={
|
|
10043
10193
|
"Cache-Control": "no-cache",
|
|
@@ -10466,7 +10616,9 @@ async def messages(request: Request):
|
|
|
10466
10616
|
await asyncio.to_thread(
|
|
10467
10617
|
_record_project_telemetry, body, model, anthropic_resp.get("usage", {})
|
|
10468
10618
|
)
|
|
10469
|
-
|
|
10619
|
+
# Wire copy only: telemetry above and monitor accounting keep the
|
|
10620
|
+
# honest numbers; the client sees the compaction-forcing scale.
|
|
10621
|
+
return _scale_client_usage(anthropic_resp)
|
|
10470
10622
|
|
|
10471
10623
|
|
|
10472
10624
|
@app.post("/anthropic/v1/messages")
|