@agentdeck/bridge 1.1.0 → 1.2.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.
- package/README.md +14 -6
- package/assets/fonts/NOTICE-PocketSansWorld.md +10 -0
- package/assets/fonts/OFL-PocketSansWorld.txt +93 -0
- package/assets/fonts/PocketSansWorld_12.cpfont +0 -0
- package/assets/fonts/pocket-sans-world.manifest.json +49 -0
- package/assets/learning/LICENSE-CC-BY-SA-4.0.txt +10 -0
- package/assets/learning/NOTICE.md +33 -0
- package/assets/learning/jp-n3-ko.manifest.json +27 -0
- package/assets/learning/jp-n3-ko.pdl +0 -0
- package/dist/agent-cli-diagnostics.d.ts +20 -0
- package/dist/agent-cli-diagnostics.d.ts.map +1 -0
- package/dist/agent-cli-diagnostics.js +100 -0
- package/dist/agent-cli-diagnostics.js.map +1 -0
- package/dist/apme/collector.d.ts +18 -1
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +48 -3
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/dashboard-html.d.ts.map +1 -1
- package/dist/apme/dashboard-html.js +275 -35
- package/dist/apme/dashboard-html.js.map +1 -1
- package/dist/apme/http.d.ts.map +1 -1
- package/dist/apme/http.js +33 -5
- package/dist/apme/http.js.map +1 -1
- package/dist/apme/index.d.ts.map +1 -1
- package/dist/apme/index.js +6 -2
- package/dist/apme/index.js.map +1 -1
- package/dist/apme/store.d.ts +41 -1
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +153 -27
- package/dist/apme/store.js.map +1 -1
- package/dist/bridge-core.d.ts +15 -0
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +30 -4
- package/dist/bridge-core.js.map +1 -1
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +55 -8
- package/dist/cli.js.map +1 -1
- package/dist/codex-rate-limits-live.d.ts +180 -10
- package/dist/codex-rate-limits-live.d.ts.map +1 -1
- package/dist/codex-rate-limits-live.js +475 -22
- package/dist/codex-rate-limits-live.js.map +1 -1
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +263 -1
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-ws-client.d.ts +1 -0
- package/dist/daemon-ws-client.d.ts.map +1 -1
- package/dist/daemon-ws-client.js +25 -16
- package/dist/daemon-ws-client.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/esp32-serial.d.ts +4 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +4 -0
- package/dist/esp32-serial.js.map +1 -1
- package/dist/font-pack.d.ts +15 -0
- package/dist/font-pack.d.ts.map +1 -0
- package/dist/font-pack.js +75 -0
- package/dist/font-pack.js.map +1 -0
- package/dist/glance-frame.d.ts.map +1 -1
- package/dist/glance-frame.js +2 -0
- package/dist/glance-frame.js.map +1 -1
- package/dist/http-auth-gate.d.ts.map +1 -1
- package/dist/http-auth-gate.js +9 -1
- package/dist/http-auth-gate.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/learning-pack.d.ts +24 -0
- package/dist/learning-pack.d.ts.map +1 -0
- package/dist/learning-pack.js +125 -0
- package/dist/learning-pack.js.map +1 -0
- package/dist/mdns.js +1 -1
- package/dist/mdns.js.map +1 -1
- package/dist/pairing-knocks.d.ts +80 -0
- package/dist/pairing-knocks.d.ts.map +1 -0
- package/dist/pairing-knocks.js +312 -0
- package/dist/pairing-knocks.js.map +1 -0
- package/dist/pixoo/micro-glyphs.d.ts +4 -0
- package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
- package/dist/pixoo/micro-glyphs.js +29 -0
- package/dist/pixoo/micro-glyphs.js.map +1 -1
- package/dist/pixoo/pixoo-font.d.ts.map +1 -1
- package/dist/pixoo/pixoo-font.js +3 -1
- package/dist/pixoo/pixoo-font.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +17 -2
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pty-manager.d.ts +9 -0
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +63 -4
- package/dist/pty-manager.js.map +1 -1
- package/dist/relayed-usage.d.ts +30 -0
- package/dist/relayed-usage.d.ts.map +1 -1
- package/dist/relayed-usage.js +25 -2
- package/dist/relayed-usage.js.map +1 -1
- package/dist/surface-protocol.d.ts.map +1 -1
- package/dist/surface-protocol.js +2 -0
- package/dist/surface-protocol.js.map +1 -1
- package/dist/tui/dashboard.d.ts.map +1 -1
- package/dist/tui/dashboard.js +8 -1
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/ws-server.d.ts +18 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +40 -2
- package/dist/ws-server.js.map +1 -1
- package/package.json +4 -3
- package/src/timebox/sync_ble.py +35 -16
|
@@ -13,7 +13,26 @@ export function apmeDashboardHtml() {
|
|
|
13
13
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
14
14
|
<title>AgentDeck — Agent Performance Monitoring & Evaluation</title>
|
|
15
15
|
<style>
|
|
16
|
-
|
|
16
|
+
/* Palette derived from design/tokens.css. The dashboard ships as one
|
|
17
|
+
* self-contained HTML string (WKWebView loads the bundled copy via the
|
|
18
|
+
* in-process daemon), so the canonical CSS file can't be <link>'d: the
|
|
19
|
+
* :root primitives below are a manual mirror of design/tokens.css §2,
|
|
20
|
+
* gated by design/verify-tokens-sync.py against the bundled resource —
|
|
21
|
+
* which is byte-identical to this renderer's output. Everything below
|
|
22
|
+
* :root references var(--…) only; the checker flags any stray hex. */
|
|
23
|
+
:root{
|
|
24
|
+
/* === Design system primitives (mirrors design/tokens.css §2) === */
|
|
25
|
+
--ink-800:#15302f;--ink-700:#1f4544;--ink-500:#426664;--ink-300:#7c9694;
|
|
26
|
+
--kelp-500:#2f8a7c;--kelp-300:#6fb6a8;
|
|
27
|
+
--coral-500:#c0573a;--amber-500:#c8923a;
|
|
28
|
+
--ui-ok:#52D988;--ui-attn:#FFA93D;--ui-error:#FF6B6B;--ui-cyan:#3ED6E8;
|
|
29
|
+
--ui-idle-dark:#7a8a9c;--ui-popup-bg-dark:#0a1a2a;--ui-tty-text:#c8d0d8;
|
|
30
|
+
/* === Dashboard-local semantic aliases === */
|
|
31
|
+
--bg:var(--ui-popup-bg-dark);--surface:var(--ink-800);--border:var(--ink-700);
|
|
32
|
+
--text:var(--ui-tty-text);--muted:var(--ui-idle-dark);--dim:var(--ink-500);
|
|
33
|
+
--green:var(--ui-ok);--yellow:var(--ui-attn);--red:var(--ui-error);--blue:var(--ui-cyan);
|
|
34
|
+
--accent:var(--kelp-500);--amber:var(--amber-500);--orange:var(--ui-attn)
|
|
35
|
+
}
|
|
17
36
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
18
37
|
body{background:var(--bg);color:var(--text);font-family:system-ui,-apple-system,sans-serif;font-size:13px;height:100vh;overflow:hidden}
|
|
19
38
|
|
|
@@ -39,10 +58,10 @@ header .status{margin-left:auto;font-size:11px;color:var(--dim)}
|
|
|
39
58
|
.table-wrap{flex:1;overflow-y:auto}
|
|
40
59
|
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
41
60
|
th{position:sticky;top:0;text-align:left;color:var(--dim);font-weight:500;padding:8px 10px;background:var(--surface);border-bottom:1px solid var(--border);font-size:11px;text-transform:uppercase;letter-spacing:0.3px}
|
|
42
|
-
td{padding:7px 10px;border-bottom:1px solid
|
|
61
|
+
td{padding:7px 10px;border-bottom:1px solid color-mix(in srgb,var(--border) 50%,transparent);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px}
|
|
43
62
|
tr{cursor:pointer;transition:background 0.1s}
|
|
44
|
-
tr:hover td{background:
|
|
45
|
-
tr.selected td{background:
|
|
63
|
+
tr:hover td{background:color-mix(in srgb,var(--surface) 80%,transparent)}
|
|
64
|
+
tr.selected td{background:color-mix(in srgb,var(--accent) 15%,transparent);border-left:2px solid var(--accent)}
|
|
46
65
|
.task-col{max-width:250px;color:var(--muted);font-style:italic}
|
|
47
66
|
|
|
48
67
|
/* ── Scores / badges ── */
|
|
@@ -52,12 +71,12 @@ tr.selected td{background:rgba(99,102,241,0.15);border-left:2px solid var(--acce
|
|
|
52
71
|
.score-low{color:var(--red)}
|
|
53
72
|
.score-na{color:var(--dim)}
|
|
54
73
|
.badge{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:600;display:inline-block}
|
|
55
|
-
.badge-committed{background
|
|
56
|
-
.badge-abandoned{background
|
|
57
|
-
.badge-iterated{background
|
|
74
|
+
.badge-committed{background:color-mix(in srgb,var(--green) 18%,var(--bg));color:var(--green)}
|
|
75
|
+
.badge-abandoned{background:color-mix(in srgb,var(--red) 18%,var(--bg));color:var(--red)}
|
|
76
|
+
.badge-iterated{background:color-mix(in srgb,var(--yellow) 18%,var(--bg));color:var(--yellow)}
|
|
58
77
|
.badge-exploratory{background:var(--surface);color:var(--muted);border:1px solid var(--border)}
|
|
59
78
|
.badge-pending{background:var(--surface);color:var(--dim)}
|
|
60
|
-
.badge-cat{background:var(--surface);color:var(--accent);border:1px solid
|
|
79
|
+
.badge-cat{background:var(--surface);color:var(--accent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);font-size:10px;padding:1px 6px;border-radius:4px}
|
|
61
80
|
|
|
62
81
|
/* ── Detail panel ── */
|
|
63
82
|
.detail-empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dim);font-size:14px}
|
|
@@ -93,11 +112,35 @@ tr.selected td{background:rgba(99,102,241,0.15);border-left:2px solid var(--acce
|
|
|
93
112
|
.vibe-bar{display:flex;gap:8px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border)}
|
|
94
113
|
.vibe-btn{padding:8px 20px;border-radius:8px;border:none;cursor:pointer;font-size:13px;font-weight:600;transition:all 0.15s}
|
|
95
114
|
.vibe-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,0.3)}
|
|
96
|
-
.vibe-approve{background
|
|
97
|
-
.vibe-reject{background
|
|
115
|
+
.vibe-approve{background:color-mix(in srgb,var(--green) 22%,var(--bg));color:var(--green)}
|
|
116
|
+
.vibe-reject{background:color-mix(in srgb,var(--red) 22%,var(--bg));color:var(--red)}
|
|
98
117
|
.vibe-current{font-size:11px;color:var(--dim);display:flex;align-items:center;margin-left:8px}
|
|
99
118
|
|
|
100
119
|
.panel{display:none}.panel.visible{display:block;height:100%}
|
|
120
|
+
|
|
121
|
+
/* ── Work board ── */
|
|
122
|
+
.work-tabs{display:flex;gap:4px;padding:8px 10px;background:var(--surface);border-bottom:1px solid var(--border);flex-wrap:wrap;align-items:center}
|
|
123
|
+
.work-tab{padding:3px 10px;border-radius:10px;font-size:11px;color:var(--dim);cursor:pointer;border:1px solid transparent;background:none}
|
|
124
|
+
.work-tab:hover{color:var(--muted)}
|
|
125
|
+
.work-tab.active{background:color-mix(in srgb,var(--accent) 15%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 30%,transparent)}
|
|
126
|
+
.work-tab .n{opacity:0.75;margin-left:3px;font-variant-numeric:tabular-nums}
|
|
127
|
+
.work-row{padding:9px 12px;border-bottom:1px solid color-mix(in srgb,var(--border) 50%,transparent);cursor:pointer}
|
|
128
|
+
.work-row:hover{background:color-mix(in srgb,var(--surface) 80%,transparent)}
|
|
129
|
+
.work-row.selected{background:color-mix(in srgb,var(--accent) 15%,transparent)}
|
|
130
|
+
.work-row.attn{box-shadow:inset 3px 0 0 var(--yellow)}
|
|
131
|
+
.work-title{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--text);font-weight:600;min-width:0}
|
|
132
|
+
.work-title .t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
133
|
+
.work-title .t.unnamed{color:var(--muted);font-weight:500;font-style:italic}
|
|
134
|
+
.work-sub{display:flex;gap:10px;flex-wrap:wrap;align-items:center;font-size:10.5px;color:var(--dim);margin-top:3px;font-variant-numeric:tabular-nums}
|
|
135
|
+
.session-link{appearance:none;background:transparent;color:var(--accent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);border-radius:9px;padding:1px 6px;font:inherit;cursor:pointer;white-space:nowrap}
|
|
136
|
+
.session-link:hover{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--text)}
|
|
137
|
+
.work-scope{display:none;align-items:center;gap:8px;padding:6px 10px;background:color-mix(in srgb,var(--accent) 9%,var(--surface));border-bottom:1px solid color-mix(in srgb,var(--accent) 25%,var(--border));font-size:11px;color:var(--muted)}
|
|
138
|
+
.work-scope code{color:var(--text);font-size:10.5px}
|
|
139
|
+
.work-scope .clear{margin-left:auto;background:transparent;color:var(--muted);border:1px solid var(--border);border-radius:9px;padding:1px 7px;cursor:pointer}
|
|
140
|
+
.chip{font-size:10px;padding:1px 7px;border-radius:9px;background:var(--surface);color:var(--muted);border:1px solid var(--border);white-space:nowrap}
|
|
141
|
+
.chip-ok{background:color-mix(in srgb,var(--green) 12%,transparent);color:var(--green);border-color:color-mix(in srgb,var(--green) 30%,transparent)}
|
|
142
|
+
.chip-warn{background:color-mix(in srgb,var(--yellow) 12%,transparent);color:var(--yellow);border-color:color-mix(in srgb,var(--yellow) 30%,transparent)}
|
|
143
|
+
.chip-bad{background:color-mix(in srgb,var(--red) 12%,transparent);color:var(--red);border-color:color-mix(in srgb,var(--red) 30%,transparent)}
|
|
101
144
|
/* Graph needs a column flex box so the canvas can claim the leftover height. */
|
|
102
145
|
.panel#panel-graph.visible{display:flex;flex-direction:column}
|
|
103
146
|
.activity-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:8px;padding:10px;background:var(--surface);border-bottom:1px solid var(--border)}
|
|
@@ -118,7 +161,8 @@ tr.selected td{background:rgba(99,102,241,0.15);border-left:2px solid var(--acce
|
|
|
118
161
|
<!-- Left: runs list + tabs -->
|
|
119
162
|
<div class="left">
|
|
120
163
|
<div class="tabs">
|
|
121
|
-
<button class="tab active" onclick="showTab('
|
|
164
|
+
<button class="tab active" onclick="showTab('work')">Work</button>
|
|
165
|
+
<button class="tab" onclick="showTab('activity')">Activity</button>
|
|
122
166
|
<button class="tab" onclick="showTab('runs')">Runs</button>
|
|
123
167
|
<button class="tab" onclick="showTab('tasks')">Tasks</button>
|
|
124
168
|
<button class="tab" onclick="showTab('graph')">Graph</button>
|
|
@@ -127,7 +171,23 @@ tr.selected td{background:rgba(99,102,241,0.15);border-left:2px solid var(--acce
|
|
|
127
171
|
<button class="tab" onclick="showTab('categories')">Categories</button>
|
|
128
172
|
</div>
|
|
129
173
|
<div class="table-wrap">
|
|
130
|
-
|
|
174
|
+
<!-- Work board: task-first default view. One row = intent title +
|
|
175
|
+
outcome/score, sub-line = boundary provenance chip + folded actions.
|
|
176
|
+
Lifecycle sub-tabs are triage (attention first, then recency) and
|
|
177
|
+
their badge counts come from the same SQL bucket definitions as the
|
|
178
|
+
filter, so tab and rows cannot disagree. -->
|
|
179
|
+
<div class="panel visible" id="panel-work">
|
|
180
|
+
<div class="work-tabs" id="work-tabs"></div>
|
|
181
|
+
<div style="display:flex;gap:6px;padding:6px 10px;background:var(--surface);border-bottom:1px solid var(--border);flex-wrap:wrap">
|
|
182
|
+
<input id="w-q" placeholder="Search prompt / summary" oninput="workSearchDebounced()" style="background:var(--bg);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:4px 8px;font-size:11px;flex:1;min-width:140px">
|
|
183
|
+
<select id="w-agent" onchange="loadWork(0)" style="background:var(--bg);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:4px 8px;font-size:11px"><option value="">All Agents</option></select>
|
|
184
|
+
<select id="w-project" onchange="loadWork(0)" style="background:var(--bg);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:4px 8px;font-size:11px"><option value="">All Projects</option></select>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="work-scope" id="work-scope"></div>
|
|
187
|
+
<div id="work-rows"></div>
|
|
188
|
+
<div id="work-pager" style="display:flex;gap:8px;align-items:center;justify-content:center;padding:10px;font-size:11px;color:var(--dim)"></div>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="panel" id="panel-activity">
|
|
131
191
|
<div class="activity-cards" id="activity-cards"></div>
|
|
132
192
|
<table><thead><tr>
|
|
133
193
|
<th>Agent</th><th>Task</th><th>Project</th><th>When</th><th>Time</th>
|
|
@@ -207,6 +267,7 @@ function showTab(n){
|
|
|
207
267
|
if(n==='tasks'&&!tasksLoaded){tasksLoaded=true;loadTasks(0)}
|
|
208
268
|
if(n==='graph'){requestAnimationFrame(()=>loadGraph())}
|
|
209
269
|
if(n==='activity'){loadActivity()}
|
|
270
|
+
if(n==='work'){loadWork(workOffset)}
|
|
210
271
|
}
|
|
211
272
|
function fs(s){if(s==null)return'<span class="score score-na">—</span>';const p=Math.round(s*100),c=p>=70?'high':p>=40?'mid':'low';return'<span class="score score-'+c+'">'+p+'%</span>'}
|
|
212
273
|
function fo(o){if(!o)return'';return'<span class="badge badge-'+o+'">'+o+'</span>'}
|
|
@@ -221,7 +282,8 @@ async function loadActivity(){
|
|
|
221
282
|
cards.innerHTML=(d.agents||[]).map(a=>'<div class="activity-card"><div class="agent">'+esc(a.agentType)+'</div><div class="time">'+fd(a.durationMs)+'</div><div class="count">'+a.taskCount+' task'+(a.taskCount===1?'':'s')+'</div></div>').join('')||'<div class="empty">No activity yet</div>';
|
|
222
283
|
const body=document.getElementById('activity-body');
|
|
223
284
|
body.innerHTML=activityRows.map((x,i)=>'<tr onclick="selectActivity('+i+')"><td>'+esc(x.agentType)+'</td><td class="task-col" title="'+esc(x.task)+'">'+esc(x.task.slice(0,90))+'</td><td>'+esc(x.projectName||'—')+'</td><td>'+new Date(x.startedAt).toLocaleString([],{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'})+'</td><td>'+fd(x.durationMs)+'</td></tr>').join('')||'<tr><td colspan="5" class="empty">No collected tasks yet</td></tr>';
|
|
224
|
-
document.getElementById('
|
|
285
|
+
if(document.getElementById('panel-activity').classList.contains('visible'))
|
|
286
|
+
document.getElementById('status').textContent=activityRows.length+' tasks · '+new Date().toLocaleTimeString();
|
|
225
287
|
}catch(e){document.getElementById('status').textContent='Error: '+e.message}
|
|
226
288
|
}
|
|
227
289
|
function selectActivity(i){
|
|
@@ -323,6 +385,7 @@ async function selectRun(id){
|
|
|
323
385
|
const startedAt=run.started_at??run.startedAt;
|
|
324
386
|
const endedAt=run.ended_at??run.endedAt;
|
|
325
387
|
const agentType=run.agent_type??run.agentType??'';
|
|
388
|
+
const sessionId=run.session_id??run.sessionId??'';
|
|
326
389
|
const modelId=run.model_id??run.modelId??'—';
|
|
327
390
|
const projectName=run.project_name??run.projectName??'—';
|
|
328
391
|
const taskPrompt=run.task_prompt??run.taskPrompt;
|
|
@@ -342,6 +405,7 @@ async function selectRun(id){
|
|
|
342
405
|
h+='<span>🕐 '+tm+(dur?' · '+fd(dur):'')+'</span>';
|
|
343
406
|
if(taskCategory)h+='<span><span class="badge-cat">'+taskCategory+'</span></span>';
|
|
344
407
|
h+='<span style="font-family:monospace;font-size:11px;color:var(--dim);cursor:pointer" onclick="navigator.clipboard.writeText(\\''+run.id+'\\');this.textContent=\\'copied!\\';;setTimeout(()=>this.textContent=\\''+run.id.slice(0,12)+'\\',1000)" title="Click to copy full ID">'+run.id.slice(0,12)+'</span>';
|
|
408
|
+
if(sessionId)h+='<button class="session-link" data-session="'+esc(sessionId)+'" data-project="'+esc(projectName==='—'?'':projectName)+'" data-agent="'+esc(agentType)+'" onclick="openWorkSessionFrom(this,event)" title="Open every task collected from this native session">View session work</button>';
|
|
345
409
|
h+='</div></div>';
|
|
346
410
|
|
|
347
411
|
// Task prompt
|
|
@@ -352,7 +416,7 @@ async function selectRun(id){
|
|
|
352
416
|
|
|
353
417
|
// Active session notice
|
|
354
418
|
if(!endedAt){
|
|
355
|
-
h+='<div class="section" style="padding:10px 12px;background:
|
|
419
|
+
h+='<div class="section" style="padding:10px 12px;background:color-mix(in srgb,var(--blue) 15%,transparent);border:1px solid color-mix(in srgb,var(--blue) 30%,transparent);border-radius:6px;color:var(--blue);font-size:12px">⏳ Session active — score, category, and outcome appear after the session ends.</div>';
|
|
356
420
|
}else{
|
|
357
421
|
// Completed but un-scored: no composite, no LLM/task/turn judge, no
|
|
358
422
|
// deterministic, no manual review. Without this, the panel reads as a
|
|
@@ -362,7 +426,7 @@ async function selectRun(id){
|
|
|
362
426
|
const scored=compScore!=null||evals.length>0;
|
|
363
427
|
if(!scored){
|
|
364
428
|
const reason=layer1Skipped?('deterministic checks skipped ('+esc(layer1Skipped)+') and no LLM judge ran'):'no evaluation ran for this task';
|
|
365
|
-
h+='<div class="section" style="padding:10px 12px;background:
|
|
429
|
+
h+='<div class="section" style="padding:10px 12px;background:color-mix(in srgb,var(--muted) 12%,transparent);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--muted);line-height:1.5">🔍 Not evaluated — '+reason+'.<br>Turn on a judge (Anthropic API, OpenClaw, local MLX 8B+, or Apple Intelligence) in <code>apme.judge</code> to score future tasks. Tasks without a judge still record their trajectory, cost, and outcome — they just carry no quality score.</div>';
|
|
366
430
|
}
|
|
367
431
|
}
|
|
368
432
|
|
|
@@ -389,7 +453,7 @@ async function selectRun(id){
|
|
|
389
453
|
h+='<div class="section"><div class="section-head"><span>Manual Reviews <span style="font-size:10px;color:var(--dim);border:1px solid var(--dim);border-radius:8px;padding:1px 6px;margin-left:6px">hand-run</span></span><span style="font-size:11px;color:var(--dim)">'+mEvals.length+'</span></div>';
|
|
390
454
|
for(const e of mEvals){
|
|
391
455
|
const risk=e.score>=1?'low':e.score>=0.5?'medium':'high';
|
|
392
|
-
const rc=risk==='high'?'var(--red)':risk==='medium'?'var(--amber
|
|
456
|
+
const rc=risk==='high'?'var(--red)':risk==='medium'?'var(--amber)':'var(--green)';
|
|
393
457
|
let summary='',findings=0;
|
|
394
458
|
if(e.raw){try{const r=JSON.parse(e.raw);summary=r.summary||'';findings=(r.findings||[]).length;}catch{}}
|
|
395
459
|
h+='<div style="padding:6px 0;border-bottom:1px solid var(--border,rgba(255,255,255,.06))">';
|
|
@@ -473,7 +537,7 @@ async function selectRun(id){
|
|
|
473
537
|
if(cscore!=null)h+=fs(cscore);else h+='<span style="font-size:11px;color:var(--dim)">…</span>';
|
|
474
538
|
h+='</div>';
|
|
475
539
|
const ocGlyph=oc==='success'?'✓':oc==='partial'?'△':oc==='fail'?'✗':oc==='abandoned'?'⊘':'';
|
|
476
|
-
const ocColor=oc==='success'?'var(--green)':oc==='fail'?'var(--red)':oc==='partial'?'var(--orange
|
|
540
|
+
const ocColor=oc==='success'?'var(--green)':oc==='fail'?'var(--red)':oc==='partial'?'var(--orange)':oc==='abandoned'?'var(--dim)':'var(--dim)';
|
|
477
541
|
if(cat||oc)h+='<div style="margin-top:4px">'+(cat?'<span class="badge-cat">'+cat+'</span>':'')+(oc?' <span style="font-size:11px;color:'+ocColor+';margin-left:6px">'+ocGlyph+' '+oc+'</span>':'')+'</div>';
|
|
478
542
|
if(summary)h+='<div style="color:var(--muted);font-size:12px;margin:6px 0 0;padding:6px 8px;background:var(--bg);border-radius:4px;border-left:2px solid var(--accent)">'+esc(summary.slice(0,500))+'</div>';
|
|
479
543
|
h+='</div>';
|
|
@@ -558,7 +622,7 @@ async function loadTasks(offset){
|
|
|
558
622
|
if(!tasks.length){body.innerHTML='<tr><td colspan="9" class="empty">No tasks match</td></tr>';document.getElementById('tasks-pager').textContent='';return}
|
|
559
623
|
let h='';
|
|
560
624
|
for(const t of tasks){
|
|
561
|
-
const label=t.summary||t.firstPrompt||('Task '+t.taskIndex);
|
|
625
|
+
const label=t.summary||t.firstPrompt||('Task '+(t.taskIndex+1));
|
|
562
626
|
// A unit whose replies were never archived can only be partly judged —
|
|
563
627
|
// say so on the row instead of letting a blank score read as "not run".
|
|
564
628
|
const gap=t.turnCount>t.answeredTurns
|
|
@@ -592,6 +656,160 @@ function fillTaskFacets(f){
|
|
|
592
656
|
}
|
|
593
657
|
function taskSearchDebounced(){clearTimeout(taskSearchTimer);taskSearchTimer=setTimeout(()=>loadTasks(0),250)}
|
|
594
658
|
|
|
659
|
+
/* ── Work board ────────────────────────────────────────────────────────────
|
|
660
|
+
Task-first default view. Rows come from /apme/tasks (server-paged), whose
|
|
661
|
+
ordering is attention-first, then recency. The lifecycle sub-tabs map to
|
|
662
|
+
the endpoint's \`view\` buckets; badge counts ride the same response so a
|
|
663
|
+
tab can never disagree with its rows. */
|
|
664
|
+
let workOffset=0,workTotal=0,workView='',workSearchTimer=null,workCounts=null,workSession='',workSessionMeta=null;const WORK_PAGE=50;
|
|
665
|
+
const WORK_VIEWS=[['','All'],['attention','Attention'],['judged','Judged'],['reported','Reported'],['inprogress','In progress'],['orphaned','Orphaned']];
|
|
666
|
+
const SIG_LABEL={todo_complete:'TODO done',clear:'/clear',session_end:'Session end',manual:'Manual',idle_gap:'Idle gap',orphaned:'Orphaned',open:'open'};
|
|
667
|
+
/* Boundary provenance: explicit (user/agent said so) > inferred (idle gap) >
|
|
668
|
+
default (session end) > reaper (orphaned). The chip color says how much to
|
|
669
|
+
trust the segmentation, not how well the task went. */
|
|
670
|
+
function sigChip(sig){
|
|
671
|
+
const cls=(sig==='clear'||sig==='manual'||sig==='todo_complete')?'chip chip-ok'
|
|
672
|
+
:sig==='idle_gap'?'chip chip-warn'
|
|
673
|
+
:sig==='orphaned'?'chip chip-bad':'chip';
|
|
674
|
+
return '<span class="'+cls+'" title="task boundary source">'+esc(SIG_LABEL[sig]||sig||'—')+'</span>';
|
|
675
|
+
}
|
|
676
|
+
function ago(ts){
|
|
677
|
+
if(!ts)return'—';const s=Math.max(0,Math.round((Date.now()-ts)/1000));
|
|
678
|
+
if(s<60)return s+'s ago';if(s<3600)return Math.floor(s/60)+'m ago';
|
|
679
|
+
if(s<86400)return Math.floor(s/3600)+'h ago';return Math.floor(s/86400)+'d ago';
|
|
680
|
+
}
|
|
681
|
+
function renderWorkTabs(){
|
|
682
|
+
const counts=workCounts||{};
|
|
683
|
+
document.getElementById('work-tabs').innerHTML=WORK_VIEWS.map(([v,label])=>{
|
|
684
|
+
const n=v===''?counts.all:counts[v];
|
|
685
|
+
return '<button class="work-tab'+(workView===v?' active':'')+'" onclick="setWorkView(\\''+v+'\\')">'+label+
|
|
686
|
+
(n!=null?'<span class="n">'+n+'</span>':'')+'</button>';
|
|
687
|
+
}).join('')+'<span style="margin-left:auto;font-size:10px;color:var(--dim)">'+(workSession?'session · recent first':'attention first, then recency')+'</span>';
|
|
688
|
+
}
|
|
689
|
+
function setWorkView(v){workView=v;loadWork(0)}
|
|
690
|
+
function shortSession(id){
|
|
691
|
+
if(!id)return'unknown';
|
|
692
|
+
if(id.includes(':'))return id.length>22?'…'+id.slice(-20):id;
|
|
693
|
+
return id.length>10?id.slice(0,8)+'…':id;
|
|
694
|
+
}
|
|
695
|
+
function renderWorkScope(){
|
|
696
|
+
const el=document.getElementById('work-scope');
|
|
697
|
+
if(!workSession){el.style.display='none';el.innerHTML='';return}
|
|
698
|
+
const m=workSessionMeta||{};
|
|
699
|
+
el.style.display='flex';
|
|
700
|
+
el.innerHTML='<strong style="color:var(--accent)">Session</strong>'+
|
|
701
|
+
(m.project?'<span>'+esc(m.project)+'</span>':'')+
|
|
702
|
+
(m.agent?'<span class="chip">'+esc(m.agent)+'</span>':'')+
|
|
703
|
+
'<code title="'+esc(workSession)+'">'+esc(shortSession(workSession))+'</code>'+
|
|
704
|
+
'<span>'+workTotal+' task'+(workTotal===1?'':'s')+'</span>'+
|
|
705
|
+
'<button class="clear" onclick="clearWorkSession()" title="Return to all sessions">All sessions ×</button>';
|
|
706
|
+
}
|
|
707
|
+
function setWorkSession(id,project,agent){
|
|
708
|
+
if(!id)return;
|
|
709
|
+
workSession=id;workSessionMeta={project:project||'',agent:agent||''};workView='';
|
|
710
|
+
document.getElementById('w-q').value='';
|
|
711
|
+
document.getElementById('w-agent').value='';
|
|
712
|
+
document.getElementById('w-project').value='';
|
|
713
|
+
loadWork(0);
|
|
714
|
+
}
|
|
715
|
+
function clearWorkSession(){workSession='';workSessionMeta=null;workView='';loadWork(0)}
|
|
716
|
+
function openWorkSessionFrom(el,ev){
|
|
717
|
+
if(ev)ev.stopPropagation();
|
|
718
|
+
const id=el.dataset.session||'';if(!id)return;
|
|
719
|
+
const visible=document.getElementById('panel-work').classList.contains('visible');
|
|
720
|
+
if(visible){setWorkSession(id,el.dataset.project||'',el.dataset.agent||'');return}
|
|
721
|
+
// Switching from Run/Task detail: prime the scope first, then let showTab's
|
|
722
|
+
// single load render it. Calling setWorkSession here would fetch twice.
|
|
723
|
+
workSession=id;workSessionMeta={project:el.dataset.project||'',agent:el.dataset.agent||''};workView='';
|
|
724
|
+
document.getElementById('w-q').value='';
|
|
725
|
+
document.getElementById('w-agent').value='';
|
|
726
|
+
document.getElementById('w-project').value='';
|
|
727
|
+
showTabById('work');
|
|
728
|
+
}
|
|
729
|
+
async function loadWork(offset){
|
|
730
|
+
workOffset=offset||0;
|
|
731
|
+
const rowsEl=document.getElementById('work-rows');
|
|
732
|
+
const p=new URLSearchParams();
|
|
733
|
+
p.set('limit',String(WORK_PAGE));p.set('offset',String(workOffset));
|
|
734
|
+
if(!workSession)p.set('sort','attention');
|
|
735
|
+
if(workView)p.set('view',workView);
|
|
736
|
+
if(workSession)p.set('session',workSession);
|
|
737
|
+
const q=document.getElementById('w-q').value.trim();if(q)p.set('q',q);
|
|
738
|
+
const ag=document.getElementById('w-agent').value;if(ag)p.set('agent',ag);
|
|
739
|
+
const pr=document.getElementById('w-project').value;if(pr)p.set('project',pr);
|
|
740
|
+
try{
|
|
741
|
+
const r=await fetch(api('/apme/tasks?'+p.toString()));
|
|
742
|
+
if(!r.ok)throw new Error('HTTP '+r.status);
|
|
743
|
+
const d=await r.json();
|
|
744
|
+
const tasks=d.tasks||[];workTotal=d.total||0;workCounts=d.viewCounts||null;
|
|
745
|
+
if(workSession&&tasks[0])workSessionMeta={project:tasks[0].projectName||'',agent:tasks[0].agentType||''};
|
|
746
|
+
renderWorkTabs();
|
|
747
|
+
renderWorkScope();
|
|
748
|
+
// A page that shrank under us (idle-gap closes, filters) leaves a stale
|
|
749
|
+
// offset past the new total — clamp back instead of rendering an empty
|
|
750
|
+
// page with no pager (which the 15s tick would then refetch forever).
|
|
751
|
+
if(!tasks.length&&workTotal>0&&workOffset>0){
|
|
752
|
+
loadWork(Math.max(0,Math.floor((workTotal-1)/WORK_PAGE)*WORK_PAGE));
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const fill=(id,vals)=>{const s=document.getElementById(id);const cur=s.value;
|
|
756
|
+
while(s.options.length>1)s.remove(1);
|
|
757
|
+
(vals||[]).forEach(v=>{const o=document.createElement('option');o.value=v;o.textContent=v;s.add(o)});
|
|
758
|
+
s.value=cur;};
|
|
759
|
+
fill('w-agent',(d.facets||{}).agents);fill('w-project',(d.facets||{}).projects);
|
|
760
|
+
if(!tasks.length){rowsEl.innerHTML='<div class="empty">No tasks in this view</div>';document.getElementById('work-pager').textContent='';return}
|
|
761
|
+
let h='';
|
|
762
|
+
for(const t of tasks){
|
|
763
|
+
// Summary-first, like the timeline SSOT: the judge's outcome sentence
|
|
764
|
+
// outranks the intent title; the intent covers the unjudged majority.
|
|
765
|
+
const label=t.summary||t.title||('Task '+(t.taskIndex+1));
|
|
766
|
+
const unnamed=!t.title&&!t.summary;
|
|
767
|
+
const gap=t.turnCount>t.answeredTurns
|
|
768
|
+
?'<span title="'+(t.turnCount-t.answeredTurns)+' turn(s) archived without a reply" style="color:var(--yellow)">◍</span>':'';
|
|
769
|
+
const open=!t.endedAt;
|
|
770
|
+
const score=t.overallScore!=null?fs(t.overallScore)
|
|
771
|
+
:open?'<span class="badge badge-pending">open</span>'
|
|
772
|
+
:'<span style="font-size:10px;color:var(--dim)">unjudged</span>';
|
|
773
|
+
// Cost basis grammar: bare $ only for a KNOWN figure, ≈$ for estimates.
|
|
774
|
+
const cost=t.costUsd!=null?(t.costKnown?'$':'≈$')+t.costUsd.toFixed(2):'';
|
|
775
|
+
const turns=t.turnCount?t.turnCount+' turn'+(t.turnCount===1?'':'s'):'';
|
|
776
|
+
h+='<div class="work-row'+(t.attention?' attn':'')+(selTaskId===t.id?' selected':'')+'" id="wr-'+t.id+'" onclick="selectTask(\\''+t.id+'\\')">'+
|
|
777
|
+
'<div class="work-title"><span class="t'+(unnamed?' unnamed':'')+'" title="'+esc(t.firstPrompt||label)+'">'+esc(label)+'</span>'+gap+
|
|
778
|
+
(t.outcome?fo(t.outcome):'')+score+'</div>'+
|
|
779
|
+
'<div class="work-sub">'+
|
|
780
|
+
sigChip(open?'open':t.boundarySignal)+
|
|
781
|
+
'<span class="chip">'+esc(t.agentType||'—')+'</span>'+
|
|
782
|
+
(t.sessionId?'<button class="session-link" data-session="'+esc(t.sessionId)+'" data-project="'+esc(t.projectName||'')+'" data-agent="'+esc(t.agentType||'')+'" onclick="openWorkSessionFrom(this,event)" title="Show all work in this session">'+esc(t.projectName||'No project')+' · '+esc(shortSession(t.sessionId))+'</button>':'')+
|
|
783
|
+
(t.taskCategory?'<span class="badge-cat">'+esc(t.taskCategory)+'</span>':'')+
|
|
784
|
+
(t.coordination?'<span class="chip" title="subagent dispatches / agent-to-agent messages" style="color:var(--accent);border-color:color-mix(in srgb,var(--accent) 30%,transparent)">'+
|
|
785
|
+
(t.coordination.dispatches?'⑂×'+t.coordination.dispatches:'')+
|
|
786
|
+
(t.coordination.dispatches&&t.coordination.messages?' ':'')+
|
|
787
|
+
(t.coordination.messages?'✉×'+t.coordination.messages:'')+'</span>':'')+
|
|
788
|
+
(t.actionFold?'<span>'+esc(t.actionFold)+'</span>':'')+
|
|
789
|
+
(turns?'<span>'+turns+'</span>':'')+
|
|
790
|
+
(cost?'<span>'+cost+'</span>':'')+
|
|
791
|
+
'<span style="margin-left:auto">'+ago(t.startedAt)+'</span>'+
|
|
792
|
+
'</div></div>';
|
|
793
|
+
}
|
|
794
|
+
rowsEl.innerHTML=h;
|
|
795
|
+
const from=workTotal?workOffset+1:0,to=Math.min(workOffset+tasks.length,workTotal);
|
|
796
|
+
document.getElementById('work-pager').innerHTML=
|
|
797
|
+
'<button onclick="loadWork('+Math.max(0,workOffset-WORK_PAGE)+')" '+(workOffset<=0?'disabled':'')+' style="background:var(--surface);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:3px 10px;cursor:pointer">Prev</button>'+
|
|
798
|
+
'<span>'+from+'–'+to+' of '+workTotal+'</span>'+
|
|
799
|
+
'<button onclick="loadWork('+(workOffset+WORK_PAGE)+')" '+(to>=workTotal?'disabled':'')+' style="background:var(--surface);color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:3px 10px;cursor:pointer">Next</button>';
|
|
800
|
+
if(document.getElementById('panel-work').classList.contains('visible'))
|
|
801
|
+
document.getElementById('status').textContent=workTotal+' tasks · '+new Date().toLocaleTimeString();
|
|
802
|
+
}catch(e){
|
|
803
|
+
// Both daemons serve /apme/tasks now (Swift since 2026-08-29), but an
|
|
804
|
+
// OLDER Swift daemon build may not — say which capability is missing
|
|
805
|
+
// instead of a bare error, so a degraded board is a labeled state.
|
|
806
|
+
rowsEl.innerHTML='<div class="empty">Task list unavailable ('+esc(e.message)+').<br>'+
|
|
807
|
+
'An older daemon build may not serve /apme/tasks; Activity and Runs tabs still work.</div>';
|
|
808
|
+
document.getElementById('work-pager').textContent='';
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function workSearchDebounced(){clearTimeout(workSearchTimer);workSearchTimer=setTimeout(()=>loadWork(0),250)}
|
|
812
|
+
|
|
595
813
|
async function selectTask(id){
|
|
596
814
|
selTaskId=id;
|
|
597
815
|
const el=document.getElementById('detail-panel');
|
|
@@ -599,18 +817,30 @@ async function selectTask(id){
|
|
|
599
817
|
try{
|
|
600
818
|
const r=await fetch(api('/apme/tasks/'+id));const d=await r.json();
|
|
601
819
|
const t=d.task||{},run=d.run||{},turns=d.turns||[],evals=d.evals||[],sample=d.sample;
|
|
820
|
+
// Normalize both casings like selectRun does — an older Swift daemon
|
|
821
|
+
// build serves this detail with snake_case keys.
|
|
822
|
+
const taskIndex=t.taskIndex??t.task_index;
|
|
823
|
+
const startedAt=t.startedAt??t.started_at;
|
|
824
|
+
const endedAt=t.endedAt??t.ended_at;
|
|
825
|
+
const boundarySignal=t.boundarySignal??t.boundary_signal;
|
|
826
|
+
const costUsd=t.costUsd??t.cost_usd;
|
|
827
|
+
const taskSessionId=run.sessionId??run.session_id??'';
|
|
828
|
+
const taskAgent=run.agentType||run.agent_type||'';
|
|
829
|
+
const taskProject=run.projectName||run.project_name||'';
|
|
602
830
|
let h='';
|
|
603
|
-
h+='<div class="detail-header"><h2>'+esc(t.summary||('Task '+
|
|
604
|
-
h+='<div class="meta-row"><span>'+esc(run.agentType||'')+'</span><span>'+esc(run.modelId||'—')+'</span>'+
|
|
605
|
-
'<span>'+esc(run.projectName||'—')+'</span>'+
|
|
606
|
-
'<span>'+(
|
|
607
|
-
'<span>'+(
|
|
608
|
-
'<span>boundary '+esc(
|
|
609
|
-
|
|
610
|
-
'
|
|
831
|
+
h+='<div class="detail-header"><h2>'+esc(t.summary||('Task '+((taskIndex??0)+1)))+'</h2>';
|
|
832
|
+
h+='<div class="meta-row"><span>'+esc(run.agentType||run.agent_type||'')+'</span><span>'+esc(run.modelId||run.model_id||'—')+'</span>'+
|
|
833
|
+
'<span>'+esc(run.projectName||run.project_name||'—')+'</span>'+
|
|
834
|
+
'<span>'+(startedAt?new Date(startedAt).toLocaleString():'—')+'</span>'+
|
|
835
|
+
'<span>'+(endedAt?fd(endedAt-startedAt):'<span style="color:var(--yellow)">open</span>')+'</span>'+
|
|
836
|
+
'<span>boundary '+esc(boundarySignal||'—')+'</span>'+
|
|
837
|
+
(taskSessionId?'<button class="session-link" data-session="'+esc(taskSessionId)+'" data-project="'+esc(taskProject)+'" data-agent="'+esc(taskAgent)+'" onclick="openWorkSessionFrom(this,event)" title="Open every task collected from this native session">View session work</button>':'')+
|
|
838
|
+
'</div></div>';
|
|
839
|
+
h+='<div class="section"><div class="metric-grid">'+
|
|
840
|
+
'<div class="metric-card"><div class="lbl">Score</div><div class="val">'+fs(d.overallScore??t.compositeScore??t.composite_score)+'</div></div>'+
|
|
611
841
|
'<div class="metric-card"><div class="lbl">Turns</div><div class="val">'+turns.length+'</div></div>'+
|
|
612
842
|
'<div class="metric-card"><div class="lbl">Events</div><div class="val">'+((sample&&sample.events?sample.events.length:0))+'</div></div>'+
|
|
613
|
-
'<div class="metric-card"><div class="lbl">Cost</div><div class="val">'+(
|
|
843
|
+
'<div class="metric-card"><div class="lbl">Cost</div><div class="val">'+(costUsd!=null?'$'+costUsd.toFixed(4):'—')+'</div></div>'+
|
|
614
844
|
'</div></div>';
|
|
615
845
|
if(evals.length){
|
|
616
846
|
h+='<div class="section"><div class="section-head"><span>Evals</span></div><table><tbody>';
|
|
@@ -634,7 +864,12 @@ async function selectTask(id){
|
|
|
634
864
|
h+='</div>';
|
|
635
865
|
el.innerHTML=h;
|
|
636
866
|
}catch(e){el.innerHTML='<div class="detail-empty">Error: '+esc(e.message)+'</div>'}
|
|
637
|
-
|
|
867
|
+
// Move the selection highlight in place — a full list refetch just to move
|
|
868
|
+
// a CSS class was the dashboard's most expensive click.
|
|
869
|
+
if(document.getElementById('panel-work').classList.contains('visible')){
|
|
870
|
+
document.querySelectorAll('.work-row.selected').forEach(r=>r.classList.remove('selected'));
|
|
871
|
+
const row=document.getElementById('wr-'+id);if(row)row.classList.add('selected');
|
|
872
|
+
} else loadTasks(taskOffset);
|
|
638
873
|
}
|
|
639
874
|
|
|
640
875
|
async function loadScorecard(){
|
|
@@ -681,7 +916,7 @@ async function loadRecommend(){
|
|
|
681
916
|
h+='<table style="margin-top:4px"><thead><tr><th>Agent</th><th>Model</th><th>Quality</th><th>$/Quality</th><th>Cost</th><th>Samples</th></tr></thead><tbody>';
|
|
682
917
|
for(const s of list){
|
|
683
918
|
const isBest=s===best;
|
|
684
|
-
h+='<tr'+(isBest?' style="background:
|
|
919
|
+
h+='<tr'+(isBest?' style="background:color-mix(in srgb,var(--green) 8%,transparent)"':'')+'><td>'+(isBest?'★ ':'')+esc(s.agentType||'—')+'</td><td>'+esc((s.modelId||'—').slice(0,22))+'</td><td>'+fs(s.avgQuality)+'</td><td>'+(s.costPerQuality!=null?'$'+s.costPerQuality.toFixed(3):'—')+'</td><td>'+(s.totalCost!=null?'$'+s.totalCost.toFixed(2):'—')+'</td><td>'+s.samples+'</td></tr>';
|
|
685
920
|
}
|
|
686
921
|
h+='</tbody></table></div>';
|
|
687
922
|
}
|
|
@@ -697,7 +932,11 @@ async function loadRecommend(){
|
|
|
697
932
|
|
|
698
933
|
Colour is by node kind; radius by degree, so the derived hubs (a file many
|
|
699
934
|
tasks touched, a tool used everywhere) are the ones that read as structural. */
|
|
700
|
-
|
|
935
|
+
// Canvas 2D can't resolve var() at draw time, so node colors are read off
|
|
936
|
+
// :root once — every value is a design token, keeping the file hex-free
|
|
937
|
+
// outside the :root mirror (design/verify-tokens-sync.py).
|
|
938
|
+
const cssTok=n=>getComputedStyle(document.documentElement).getPropertyValue(n).trim();
|
|
939
|
+
const G_COLORS={run:cssTok('--ui-cyan'),task:cssTok('--ui-ok'),turn:cssTok('--ui-idle-dark'),session:cssTok('--amber-500'),project:cssTok('--kelp-300'),model:cssTok('--kelp-500'),agent:cssTok('--coral-500'),tool:cssTok('--ui-attn'),file:cssTok('--ink-300')};
|
|
701
940
|
const G_EDGE={contains:'rgba(148,163,184,0.35)',continues:'rgba(97,102,224,0.75)',produced:'rgba(234,179,8,0.30)',used:'rgba(249,115,22,0.30)',touched:'rgba(148,163,184,0.25)'};
|
|
702
941
|
|
|
703
942
|
async function loadGraph(){
|
|
@@ -810,12 +1049,12 @@ function startGraphSim(nodes,edges){
|
|
|
810
1049
|
ctx.beginPath();ctx.moveTo(l.s.x,l.s.y);ctx.lineTo(l.t.x,l.t.y);ctx.stroke();
|
|
811
1050
|
}
|
|
812
1051
|
for(const n of nodes){
|
|
813
|
-
ctx.fillStyle=G_COLORS[n.kind]||
|
|
1052
|
+
ctx.fillStyle=G_COLORS[n.kind]||G_COLORS.file;
|
|
814
1053
|
ctx.globalAlpha=hover&&hover!==n?0.55:1;
|
|
815
1054
|
ctx.beginPath();ctx.arc(n.x,n.y,n.r,0,6.2832);ctx.fill();
|
|
816
1055
|
// Label only what carries the structure, else the canvas is unreadable.
|
|
817
1056
|
if(n.r>7||n===hover){
|
|
818
|
-
ctx.globalAlpha=1;ctx.fillStyle='
|
|
1057
|
+
ctx.globalAlpha=1;ctx.fillStyle=cssTok('--ui-tty-text');ctx.font='10px ui-monospace,monospace';
|
|
819
1058
|
ctx.fillText(String(n.label||'').slice(0,26),n.x+n.r+3,n.y+3);
|
|
820
1059
|
}
|
|
821
1060
|
ctx.globalAlpha=1;
|
|
@@ -832,12 +1071,13 @@ function showTabById(n){
|
|
|
832
1071
|
if(btn)btn.click();
|
|
833
1072
|
}
|
|
834
1073
|
|
|
835
|
-
loadActivity();loadRuns();loadRecommend();loadScorecard();loadCategories();
|
|
1074
|
+
loadWork(0);loadActivity();loadRuns();loadRecommend();loadScorecard();loadCategories();
|
|
836
1075
|
setInterval(loadRuns,15000);setInterval(loadRecommend,30000);setInterval(loadScorecard,30000);setInterval(loadCategories,30000);
|
|
837
|
-
// Tasks refresh only while
|
|
838
|
-
// is no reason to run it against the store every 15s in the background.
|
|
1076
|
+
// Tasks/Work refresh only while their tab is up — the query is server-paged and
|
|
1077
|
+
// there is no reason to run it against the store every 15s in the background.
|
|
839
1078
|
setInterval(()=>{if(document.getElementById('panel-tasks').classList.contains('visible'))loadTasks(taskOffset)},15000);
|
|
840
1079
|
setInterval(()=>{if(document.getElementById('panel-activity').classList.contains('visible'))loadActivity()},15000);
|
|
1080
|
+
setInterval(()=>{if(document.getElementById('panel-work').classList.contains('visible'))loadWork(workOffset)},15000);
|
|
841
1081
|
</script>
|
|
842
1082
|
</body>
|
|
843
1083
|
</html>`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-html.js","sourceRoot":"","sources":["../../src/apme/dashboard-html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,iBAAiB;IAC/B,OAAO
|
|
1
|
+
{"version":3,"file":"dashboard-html.js","sourceRoot":"","sources":["../../src/apme/dashboard-html.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAkjCD,CAAC;AACT,CAAC"}
|
package/dist/apme/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/apme/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/apme/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAS7C,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,UAAU,GAAG,IAAI,GACtB,OAAO,CAAC,OAAO,CAAC,CAmSlB;AAyCD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAMzG"}
|
package/dist/apme/http.js
CHANGED
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { loadApmeConfig } from './settings.js';
|
|
17
17
|
import { apmeDashboardHtml } from './dashboard-html.js';
|
|
18
|
-
import { EVAL_SCHEMA_VERSION } from '@agentdeck/shared';
|
|
18
|
+
import { agentCoordinationSummary, deriveTaskTitle, foldActionCounts, EVAL_SCHEMA_VERSION } from '@agentdeck/shared';
|
|
19
19
|
import { activitySnapshotForStore } from './activity-history.js';
|
|
20
|
+
const TASK_VIEWS = ['attention', 'inprogress', 'judged', 'reported', 'orphaned'];
|
|
20
21
|
export async function handleApmeRequest(req, res, apme) {
|
|
21
22
|
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
22
23
|
if (!url.pathname.startsWith('/apme'))
|
|
@@ -113,19 +114,46 @@ export async function handleApmeRequest(req, res, apme) {
|
|
|
113
114
|
const limit = clampInt(url.searchParams.get('limit'), 1, 500, 50);
|
|
114
115
|
const offset = clampInt(url.searchParams.get('offset'), 0, 1_000_000, 0);
|
|
115
116
|
const stateParam = url.searchParams.get('state');
|
|
116
|
-
const
|
|
117
|
-
|
|
117
|
+
const viewParam = url.searchParams.get('view');
|
|
118
|
+
const view = TASK_VIEWS.find((v) => v === viewParam);
|
|
119
|
+
const filters = {
|
|
118
120
|
...(url.searchParams.get('agent') ? { agentType: url.searchParams.get('agent') } : {}),
|
|
121
|
+
...(url.searchParams.get('session') ? { sessionId: url.searchParams.get('session') } : {}),
|
|
119
122
|
...(url.searchParams.get('project') ? { projectName: url.searchParams.get('project') } : {}),
|
|
120
123
|
...(url.searchParams.get('category') ? { category: url.searchParams.get('category') } : {}),
|
|
121
124
|
...(url.searchParams.get('outcome') ? { outcome: url.searchParams.get('outcome') } : {}),
|
|
122
|
-
...(stateParam === 'closed' || stateParam === 'open' ? { state: stateParam } : {}),
|
|
123
125
|
...(url.searchParams.get('q') ? { q: url.searchParams.get('q') } : {}),
|
|
126
|
+
};
|
|
127
|
+
const { total, tasks } = apme.store.listTaskPage({
|
|
128
|
+
limit, offset,
|
|
129
|
+
...filters,
|
|
130
|
+
...(stateParam === 'closed' || stateParam === 'open' ? { state: stateParam } : {}),
|
|
131
|
+
...(view ? { view } : {}),
|
|
132
|
+
// Attention-first is the Work board's ordering and ONLY the Work
|
|
133
|
+
// board's — every other listTaskPage consumer keeps pure recency.
|
|
134
|
+
...(url.searchParams.get('sort') === 'attention' ? { order: 'attention' } : {}),
|
|
135
|
+
});
|
|
136
|
+
// Work-board display projections, enriched here so the store keeps
|
|
137
|
+
// returning raw facts: intent title (deriveTaskTitle SSOT — the same
|
|
138
|
+
// rules that name the timeline's task_start header, over the task's OWN
|
|
139
|
+
// first prompt, never the run fallback) and the folded action line
|
|
140
|
+
// (foldActionCounts SSOT) over one grouped tool query.
|
|
141
|
+
const toolCounts = apme.store.toolCountsForTasks(tasks.map((t) => t.id));
|
|
142
|
+
const enriched = tasks.map((t) => {
|
|
143
|
+
const tools = toolCounts.get(t.id) ?? [];
|
|
144
|
+
return {
|
|
145
|
+
...t,
|
|
146
|
+
title: deriveTaskTitle(t.ownFirstPrompt),
|
|
147
|
+
actionFold: foldActionCounts({ tools, filesTouched: t.filesTouched }),
|
|
148
|
+
coordination: agentCoordinationSummary(tools),
|
|
149
|
+
};
|
|
124
150
|
});
|
|
125
151
|
sendJson(res, 200, {
|
|
126
152
|
schema: EVAL_SCHEMA_VERSION,
|
|
127
|
-
total, limit, offset, tasks,
|
|
153
|
+
total, limit, offset, tasks: enriched,
|
|
128
154
|
facets: apme.store.taskFacets(),
|
|
155
|
+
// Badges honor the same narrowing filters as the rows.
|
|
156
|
+
viewCounts: apme.store.taskViewCounts(filters),
|
|
129
157
|
});
|
|
130
158
|
return true;
|
|
131
159
|
}
|