@miller-tech/uap 1.129.0 → 1.129.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/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +6 -1
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/dashboard.d.ts +2 -0
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +5 -0
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +11 -2
- package/dist/dashboard/server.js.map +1 -1
- package/dist/models/types.js +2 -2
- package/dist/models/types.js.map +1 -1
- package/docs/reference/CLI.md +1 -1
- package/docs/reference/CONFIGURATION.md +1 -0
- package/package.json +2 -2
- 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 +31 -0
- package/tools/agents/tests/test_stream_telemetry.py +82 -0
- package/web/dashboard.html +4 -1
package/docs/reference/CLI.md
CHANGED
|
@@ -557,7 +557,7 @@ Aliased as `dash`.
|
|
|
557
557
|
| `agents` | `-v` | Agent activity, resource claims, coordination status |
|
|
558
558
|
| `memory` | `-v` | Memory system health, capacity, layer architecture |
|
|
559
559
|
| `progress` | `-v` | Completion tracking per-priority and per-type |
|
|
560
|
-
| `serve` | `-p, --port` (3847) | Start the web-based dashboard server with live updates |
|
|
560
|
+
| `serve` | `-p, --port` (3847), `--host`, `--refresh <seconds>` (2) | Start the web-based dashboard server with live updates; `--refresh` sets the snapshot push/poll cadence (min 0.25s, env `UAP_DASH_REFRESH_MS`) |
|
|
561
561
|
| `stats` | `-v` | Session context consumption stats with per-tool breakdown |
|
|
562
562
|
| `session` | `-v`, `--compact` | Live UAP session state: infra, patterns, skills, git, policies |
|
|
563
563
|
| `benchmark` | `-v` | Benchmark results and performance comparison |
|
|
@@ -91,6 +91,7 @@ Top level: `version`, `project`, `memory`, `worktree`, `costOptimization`,
|
|
|
91
91
|
| `UAP_BENCHMARK_PARALLEL` | Parallel model count in benchmarks | — |
|
|
92
92
|
| `UAP_SNAPSHOT_DIR` | Base dir for `--keep-best` snapshots (absolute path) | `~/.cache/uap/snapshots` |
|
|
93
93
|
| `UAP_SNAPSHOT_MAX_MB` | Skip the `--keep-best` snapshot (rollback disabled) when the tree exceeds this size | `4096` |
|
|
94
|
+
| `UAP_DASH_REFRESH_MS` | Dashboard live snapshot push/poll interval in ms (`--refresh` flag wins; min 250) | `2000` |
|
|
94
95
|
| `HERMES_HOME` | Hermes home dir | `~/.hermes` |
|
|
95
96
|
| `FACTORY_PROJECT_DIR` | Project dir in Factory hook commands | — |
|
|
96
97
|
| `FORGE_UAP_PROJECT` | Project dir in ForgeCode hook scripts | `.` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miller-tech/uap",
|
|
3
|
-
"version": "1.129.
|
|
3
|
+
"version": "1.129.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": "python3 -m unittest tools.agents.tests.test_enforcement_self_protect tools.agents.tests.test_workdir_scope_enforcer 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",
|
|
24
|
+
"test:enforcers": "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_workdir_scope_enforcer 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",
|
|
25
25
|
"test:coverage": "vitest --coverage",
|
|
26
26
|
"bench": "vitest --config vitest.bench.config.ts",
|
|
27
27
|
"lint": "eslint src --ext .ts",
|
|
Binary file
|
|
Binary file
|
|
@@ -8954,6 +8954,12 @@ async def stream_anthropic_response(
|
|
|
8954
8954
|
text_chunks: list[str] = [] # accumulate text for logging
|
|
8955
8955
|
reasoning_chunks: list[str] = [] # accumulate reasoning for fallback
|
|
8956
8956
|
|
|
8957
|
+
# Real token counts from upstream's final usage chunk (llama-server /
|
|
8958
|
+
# OpenAI emit it on the last data frame). The per-delta output_tokens
|
|
8959
|
+
# counter is a chunk count that misses tool-call deltas entirely; prefer
|
|
8960
|
+
# upstream truth for telemetry and the session monitor.
|
|
8961
|
+
upstream_usage: dict = {}
|
|
8962
|
+
|
|
8957
8963
|
try:
|
|
8958
8964
|
async for line in openai_stream.aiter_lines():
|
|
8959
8965
|
if not line.startswith("data: "):
|
|
@@ -8966,6 +8972,9 @@ async def stream_anthropic_response(
|
|
|
8966
8972
|
except json.JSONDecodeError:
|
|
8967
8973
|
continue
|
|
8968
8974
|
|
|
8975
|
+
if isinstance(chunk.get("usage"), dict):
|
|
8976
|
+
upstream_usage = chunk["usage"]
|
|
8977
|
+
|
|
8969
8978
|
choice = (chunk.get("choices") or [{}])[0]
|
|
8970
8979
|
delta = choice.get("delta", {})
|
|
8971
8980
|
|
|
@@ -9239,6 +9248,28 @@ async def stream_anthropic_response(
|
|
|
9239
9248
|
# message_stop
|
|
9240
9249
|
yield f"event: message_stop\ndata: {json.dumps({'type': 'message_stop'})}\n\n"
|
|
9241
9250
|
|
|
9251
|
+
# Per-project telemetry for the TRUE streaming path. The only other call
|
|
9252
|
+
# sites are on the non-stream / guarded-non-stream paths, so streaming
|
|
9253
|
+
# sessions (the client default) wrote no task_outcomes rows and
|
|
9254
|
+
# per-project dashboards froze at the last non-stream response.
|
|
9255
|
+
# Prefer upstream's real usage: last_input_tokens is a char-based request
|
|
9256
|
+
# estimate, and the per-delta counter misses tool-call turns entirely.
|
|
9257
|
+
upstream_input = int(upstream_usage.get("prompt_tokens") or 0)
|
|
9258
|
+
if upstream_input > 0:
|
|
9259
|
+
monitor.last_input_tokens = upstream_input
|
|
9260
|
+
usage_final = {
|
|
9261
|
+
"input_tokens": upstream_input or getattr(monitor, "last_input_tokens", 0) or 0,
|
|
9262
|
+
"output_tokens": int(upstream_usage.get("completion_tokens") or 0) or output_tokens,
|
|
9263
|
+
}
|
|
9264
|
+
# After message_stop (never delays the client's final frame; a disconnect
|
|
9265
|
+
# parked exactly on that yield skips recording, matching the non-stream
|
|
9266
|
+
# "completed responses only" semantics) and off the event loop (a locked
|
|
9267
|
+
# analytics DB must not stall token delivery for concurrent sessions).
|
|
9268
|
+
try:
|
|
9269
|
+
await asyncio.to_thread(_record_project_telemetry, anthropic_body, model, usage_final)
|
|
9270
|
+
except Exception:
|
|
9271
|
+
pass
|
|
9272
|
+
|
|
9242
9273
|
|
|
9243
9274
|
# ===========================================================================
|
|
9244
9275
|
# API Endpoints
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""The TRUE streaming path must record per-project telemetry.
|
|
2
|
+
|
|
3
|
+
Regression for the frozen-dashboard incident (2026-07-08): the only
|
|
4
|
+
_record_project_telemetry call sites were on the non-stream / guarded
|
|
5
|
+
non-stream paths, so streaming sessions (the client default) wrote no
|
|
6
|
+
task_outcomes rows and per-project dashboards showed static data forever.
|
|
7
|
+
"""
|
|
8
|
+
import asyncio
|
|
9
|
+
import importlib.util
|
|
10
|
+
import unittest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
14
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
15
|
+
ap = importlib.util.module_from_spec(spec)
|
|
16
|
+
spec.loader.exec_module(ap)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FakeOpenAIStream:
|
|
20
|
+
"""Minimal httpx.Response stand-in: yields OpenAI SSE lines."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, lines):
|
|
23
|
+
self._lines = lines
|
|
24
|
+
|
|
25
|
+
async def aiter_lines(self):
|
|
26
|
+
for line in self._lines:
|
|
27
|
+
yield line
|
|
28
|
+
|
|
29
|
+
async def aclose(self):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
OPENAI_LINES = [
|
|
34
|
+
'data: {"choices": [{"delta": {"content": "hello"}, "finish_reason": null}]}',
|
|
35
|
+
'data: {"choices": [{"delta": {}, "finish_reason": "stop"}], "usage": {"prompt_tokens": 456, "completion_tokens": 5}}',
|
|
36
|
+
"data: [DONE]",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StreamTelemetryTest(unittest.TestCase):
|
|
41
|
+
def _drive(self, body):
|
|
42
|
+
monitor = ap.SessionMonitor()
|
|
43
|
+
monitor.last_input_tokens = 123
|
|
44
|
+
recorded = []
|
|
45
|
+
saved = ap._record_project_telemetry
|
|
46
|
+
ap._record_project_telemetry = lambda b, m, u: recorded.append((b, m, u))
|
|
47
|
+
try:
|
|
48
|
+
gen = ap.stream_anthropic_response(
|
|
49
|
+
FakeOpenAIStream(OPENAI_LINES), "test-model", monitor, body
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def consume():
|
|
53
|
+
return [frame async for frame in gen]
|
|
54
|
+
|
|
55
|
+
frames = asyncio.run(consume())
|
|
56
|
+
finally:
|
|
57
|
+
ap._record_project_telemetry = saved
|
|
58
|
+
return frames, recorded
|
|
59
|
+
|
|
60
|
+
def test_streaming_response_records_project_telemetry(self):
|
|
61
|
+
body = {"messages": [{"role": "user", "content": "hi"}]}
|
|
62
|
+
frames, recorded = self._drive(body)
|
|
63
|
+
self.assertTrue(any("message_stop" in f for f in frames))
|
|
64
|
+
self.assertEqual(len(recorded), 1)
|
|
65
|
+
recorded_body, model, usage = recorded[0]
|
|
66
|
+
self.assertIs(recorded_body, body)
|
|
67
|
+
self.assertEqual(model, "test-model")
|
|
68
|
+
# Upstream's final usage chunk wins over the request-side estimate and
|
|
69
|
+
# the per-delta chunk counter (which misses tool-call turns).
|
|
70
|
+
self.assertEqual(usage.get("input_tokens"), 456)
|
|
71
|
+
self.assertEqual(usage.get("output_tokens"), 5)
|
|
72
|
+
|
|
73
|
+
def test_telemetry_recorded_after_client_visible_stream_end(self):
|
|
74
|
+
# The recorder must not delay or reorder client frames: message_stop is
|
|
75
|
+
# emitted before the telemetry write happens.
|
|
76
|
+
frames, recorded = self._drive({"messages": []})
|
|
77
|
+
self.assertIn("message_stop", frames[-1])
|
|
78
|
+
self.assertEqual(len(recorded), 1)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
unittest.main()
|
package/web/dashboard.html
CHANGED
|
@@ -629,6 +629,9 @@
|
|
|
629
629
|
// page is opened without the server substitution (mutations then 401).
|
|
630
630
|
const DASHBOARD_TOKEN = '__UAP_DASHBOARD_TOKEN__'.startsWith('__UAP') ? '' : '__UAP_DASHBOARD_TOKEN__';
|
|
631
631
|
const MUTATION_HEADERS = { 'X-Uap-Dashboard-Token': DASHBOARD_TOKEN };
|
|
632
|
+
// Server-injected push cadence (uap dash serve --refresh); the fallback
|
|
633
|
+
// poll below matches it. 2000 when opened without server substitution.
|
|
634
|
+
const REFRESH_MS = Number('__UAP_DASH_REFRESH_MS__') > 0 ? Number('__UAP_DASH_REFRESH_MS__') : 2000;
|
|
632
635
|
let ws = null;
|
|
633
636
|
let reconnectTimer = null;
|
|
634
637
|
let reconnectDelay = 1000;
|
|
@@ -1281,7 +1284,7 @@
|
|
|
1281
1284
|
setInterval(async()=>{
|
|
1282
1285
|
if(ws&&ws.readyState===WebSocket.OPEN)return;
|
|
1283
1286
|
try{const r=await fetch(API_URL+'/api/dashboard');if(!r.ok)return;const d=await r.json();if(d)render(d);document.getElementById('ws-status').className='status-dot polling';document.getElementById('refresh-info').textContent='Polling - '+new Date().toLocaleTimeString();document.title='UAP Dashboard - Polling';}catch{}
|
|
1284
|
-
},
|
|
1287
|
+
}, REFRESH_MS);
|
|
1285
1288
|
|
|
1286
1289
|
// Resize: re-init charts
|
|
1287
1290
|
let resizeT;
|