@agentdeck/bridge 1.1.0 → 1.2.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/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/classifier.d.ts.map +1 -1
- package/dist/apme/classifier.js +18 -10
- package/dist/apme/classifier.js.map +1 -1
- package/dist/apme/claude-transcript-reader.d.ts +36 -0
- package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
- package/dist/apme/claude-transcript-reader.js +78 -2
- package/dist/apme/claude-transcript-reader.js.map +1 -1
- package/dist/apme/collector.d.ts +126 -3
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +450 -41
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/dashboard-html.d.ts.map +1 -1
- package/dist/apme/dashboard-html.js +278 -36
- package/dist/apme/dashboard-html.js.map +1 -1
- package/dist/apme/graph.d.ts +1 -1
- package/dist/apme/graph.d.ts.map +1 -1
- package/dist/apme/graph.js +51 -1
- package/dist/apme/graph.js.map +1 -1
- package/dist/apme/http.d.ts.map +1 -1
- package/dist/apme/http.js +37 -5
- package/dist/apme/http.js.map +1 -1
- package/dist/apme/index.d.ts +4 -0
- package/dist/apme/index.d.ts.map +1 -1
- package/dist/apme/index.js +19 -3
- package/dist/apme/index.js.map +1 -1
- package/dist/apme/runner.d.ts +90 -0
- package/dist/apme/runner.d.ts.map +1 -1
- package/dist/apme/runner.js +524 -48
- package/dist/apme/runner.js.map +1 -1
- package/dist/apme/sample-to-timeline.d.ts.map +1 -1
- package/dist/apme/sample-to-timeline.js +5 -0
- package/dist/apme/sample-to-timeline.js.map +1 -1
- package/dist/apme/settings.d.ts +8 -0
- package/dist/apme/settings.d.ts.map +1 -1
- package/dist/apme/settings.js +4 -0
- package/dist/apme/settings.js.map +1 -1
- package/dist/apme/store.d.ts +118 -9
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +342 -44
- package/dist/apme/store.js.map +1 -1
- package/dist/apme/task-gradeability.d.ts +82 -0
- package/dist/apme/task-gradeability.d.ts.map +1 -0
- package/dist/apme/task-gradeability.js +126 -0
- package/dist/apme/task-gradeability.js.map +1 -0
- package/dist/awaiting-overlay.d.ts +13 -26
- package/dist/awaiting-overlay.d.ts.map +1 -1
- package/dist/awaiting-overlay.js +7 -40
- package/dist/awaiting-overlay.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/claude-permission-rules.d.ts +20 -32
- package/dist/claude-permission-rules.d.ts.map +1 -1
- package/dist/claude-permission-rules.js +19 -102
- package/dist/claude-permission-rules.js.map +1 -1
- package/dist/claude-usage-recovery.d.ts +54 -0
- package/dist/claude-usage-recovery.d.ts.map +1 -0
- package/dist/claude-usage-recovery.js +200 -0
- package/dist/claude-usage-recovery.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +81 -14
- 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/codex-rollout-response.d.ts +43 -0
- package/dist/codex-rollout-response.d.ts.map +1 -1
- package/dist/codex-rollout-response.js +67 -0
- package/dist/codex-rollout-response.js.map +1 -1
- package/dist/coordination-evidence.d.ts +150 -0
- package/dist/coordination-evidence.d.ts.map +1 -0
- package/dist/coordination-evidence.js +455 -0
- package/dist/coordination-evidence.js.map +1 -0
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +592 -35
- 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/deferred-reply-read.d.ts +24 -0
- package/dist/deferred-reply-read.d.ts.map +1 -0
- package/dist/deferred-reply-read.js +42 -0
- package/dist/deferred-reply-read.js.map +1 -0
- package/dist/esp32-serial.d.ts +15 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +61 -2
- 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/hook-codex-sessions.d.ts +9 -0
- package/dist/hook-codex-sessions.d.ts.map +1 -1
- package/dist/hook-codex-sessions.js +35 -2
- package/dist/hook-codex-sessions.js.map +1 -1
- package/dist/hook-opencode-sessions.d.ts +62 -0
- package/dist/hook-opencode-sessions.d.ts.map +1 -0
- package/dist/hook-opencode-sessions.js +180 -0
- package/dist/hook-opencode-sessions.js.map +1 -0
- package/dist/hook-response-source.d.ts +40 -0
- package/dist/hook-response-source.d.ts.map +1 -0
- package/dist/hook-response-source.js +31 -0
- package/dist/hook-response-source.js.map +1 -0
- 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.d.ts.map +1 -1
- package/dist/index.js +6 -3
- 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/native-dependency-diagnostics.d.ts +29 -0
- package/dist/native-dependency-diagnostics.d.ts.map +1 -0
- package/dist/native-dependency-diagnostics.js +95 -0
- package/dist/native-dependency-diagnostics.js.map +1 -0
- package/dist/observed-steering.d.ts +4 -4
- package/dist/observed-steering.d.ts.map +1 -1
- package/dist/observed-steering.js +28 -32
- package/dist/observed-steering.js.map +1 -1
- package/dist/openclaw-gateway-stability.d.ts +94 -0
- package/dist/openclaw-gateway-stability.d.ts.map +1 -0
- package/dist/openclaw-gateway-stability.js +176 -0
- package/dist/openclaw-gateway-stability.js.map +1 -0
- 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/passive-observer.d.ts +5 -0
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +6 -0
- package/dist/passive-observer.js.map +1 -1
- 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/review-runner.js +1 -1
- package/dist/subagent-timeline.d.ts +27 -0
- package/dist/subagent-timeline.d.ts.map +1 -1
- package/dist/subagent-timeline.js +52 -5
- package/dist/subagent-timeline.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/usage-api.d.ts +3 -0
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +101 -36
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +2 -1
- package/dist/usage-event.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 +6 -5
- 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,162 @@ 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 NG={no_reply:'no reply captured',aborted_only:'ended by the client',trivial:'trivial exchange'};
|
|
771
|
+
const score=t.overallScore!=null?fs(t.overallScore)
|
|
772
|
+
:open?'<span class="badge badge-pending">open</span>'
|
|
773
|
+
:t.notGradeable?'<span class="chip" title="the judge declined this task: '+esc(NG[t.notGradeable]||t.notGradeable)+'" style="color:var(--dim)">not graded · '+esc(NG[t.notGradeable]||t.notGradeable)+'</span>'
|
|
774
|
+
:'<span style="font-size:10px;color:var(--dim)">unjudged</span>';
|
|
775
|
+
// Cost basis grammar: bare $ only for a KNOWN figure, ≈$ for estimates.
|
|
776
|
+
const cost=t.costUsd!=null?(t.costKnown?'$':'≈$')+t.costUsd.toFixed(2):'';
|
|
777
|
+
const turns=t.turnCount?t.turnCount+' turn'+(t.turnCount===1?'':'s'):'';
|
|
778
|
+
h+='<div class="work-row'+(t.attention?' attn':'')+(selTaskId===t.id?' selected':'')+'" id="wr-'+t.id+'" onclick="selectTask(\\''+t.id+'\\')">'+
|
|
779
|
+
'<div class="work-title"><span class="t'+(unnamed?' unnamed':'')+'" title="'+esc(t.firstPrompt||label)+'">'+esc(label)+'</span>'+gap+
|
|
780
|
+
(t.outcome?fo(t.outcome):'')+score+'</div>'+
|
|
781
|
+
'<div class="work-sub">'+
|
|
782
|
+
sigChip(open?'open':t.boundarySignal)+
|
|
783
|
+
'<span class="chip">'+esc(t.agentType||'—')+'</span>'+
|
|
784
|
+
(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>':'')+
|
|
785
|
+
(t.taskCategory?'<span class="badge-cat">'+esc(t.taskCategory)+'</span>':'')+
|
|
786
|
+
(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)">'+
|
|
787
|
+
(t.coordination.dispatches?'⑂×'+t.coordination.dispatches:'')+
|
|
788
|
+
(t.coordination.dispatches&&t.coordination.messages?' ':'')+
|
|
789
|
+
(t.coordination.messages?'✉×'+t.coordination.messages:'')+'</span>':'')+
|
|
790
|
+
(t.actionFold?'<span>'+esc(t.actionFold)+'</span>':'')+
|
|
791
|
+
(turns?'<span>'+turns+'</span>':'')+
|
|
792
|
+
(cost?'<span>'+cost+'</span>':'')+
|
|
793
|
+
'<span style="margin-left:auto">'+ago(t.startedAt)+'</span>'+
|
|
794
|
+
'</div></div>';
|
|
795
|
+
}
|
|
796
|
+
rowsEl.innerHTML=h;
|
|
797
|
+
const from=workTotal?workOffset+1:0,to=Math.min(workOffset+tasks.length,workTotal);
|
|
798
|
+
document.getElementById('work-pager').innerHTML=
|
|
799
|
+
'<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>'+
|
|
800
|
+
'<span>'+from+'–'+to+' of '+workTotal+'</span>'+
|
|
801
|
+
'<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>';
|
|
802
|
+
if(document.getElementById('panel-work').classList.contains('visible'))
|
|
803
|
+
document.getElementById('status').textContent=workTotal+' tasks · '+new Date().toLocaleTimeString();
|
|
804
|
+
}catch(e){
|
|
805
|
+
// Both daemons serve /apme/tasks now (Swift since 2026-08-29), but an
|
|
806
|
+
// OLDER Swift daemon build may not — say which capability is missing
|
|
807
|
+
// instead of a bare error, so a degraded board is a labeled state.
|
|
808
|
+
rowsEl.innerHTML='<div class="empty">Task list unavailable ('+esc(e.message)+').<br>'+
|
|
809
|
+
'An older daemon build may not serve /apme/tasks; Activity and Runs tabs still work.</div>';
|
|
810
|
+
document.getElementById('work-pager').textContent='';
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function workSearchDebounced(){clearTimeout(workSearchTimer);workSearchTimer=setTimeout(()=>loadWork(0),250)}
|
|
814
|
+
|
|
595
815
|
async function selectTask(id){
|
|
596
816
|
selTaskId=id;
|
|
597
817
|
const el=document.getElementById('detail-panel');
|
|
@@ -599,18 +819,30 @@ async function selectTask(id){
|
|
|
599
819
|
try{
|
|
600
820
|
const r=await fetch(api('/apme/tasks/'+id));const d=await r.json();
|
|
601
821
|
const t=d.task||{},run=d.run||{},turns=d.turns||[],evals=d.evals||[],sample=d.sample;
|
|
822
|
+
// Normalize both casings like selectRun does — an older Swift daemon
|
|
823
|
+
// build serves this detail with snake_case keys.
|
|
824
|
+
const taskIndex=t.taskIndex??t.task_index;
|
|
825
|
+
const startedAt=t.startedAt??t.started_at;
|
|
826
|
+
const endedAt=t.endedAt??t.ended_at;
|
|
827
|
+
const boundarySignal=t.boundarySignal??t.boundary_signal;
|
|
828
|
+
const costUsd=t.costUsd??t.cost_usd;
|
|
829
|
+
const taskSessionId=run.sessionId??run.session_id??'';
|
|
830
|
+
const taskAgent=run.agentType||run.agent_type||'';
|
|
831
|
+
const taskProject=run.projectName||run.project_name||'';
|
|
602
832
|
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
|
-
'
|
|
833
|
+
h+='<div class="detail-header"><h2>'+esc(t.summary||('Task '+((taskIndex??0)+1)))+'</h2>';
|
|
834
|
+
h+='<div class="meta-row"><span>'+esc(run.agentType||run.agent_type||'')+'</span><span>'+esc(run.modelId||run.model_id||'—')+'</span>'+
|
|
835
|
+
'<span>'+esc(run.projectName||run.project_name||'—')+'</span>'+
|
|
836
|
+
'<span>'+(startedAt?new Date(startedAt).toLocaleString():'—')+'</span>'+
|
|
837
|
+
'<span>'+(endedAt?fd(endedAt-startedAt):'<span style="color:var(--yellow)">open</span>')+'</span>'+
|
|
838
|
+
'<span>boundary '+esc(boundarySignal||'—')+'</span>'+
|
|
839
|
+
(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>':'')+
|
|
840
|
+
'</div></div>';
|
|
841
|
+
h+='<div class="section"><div class="metric-grid">'+
|
|
842
|
+
'<div class="metric-card"><div class="lbl">Score</div><div class="val">'+fs(d.overallScore??t.compositeScore??t.composite_score)+'</div></div>'+
|
|
611
843
|
'<div class="metric-card"><div class="lbl">Turns</div><div class="val">'+turns.length+'</div></div>'+
|
|
612
844
|
'<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">'+(
|
|
845
|
+
'<div class="metric-card"><div class="lbl">Cost</div><div class="val">'+(costUsd!=null?'$'+costUsd.toFixed(4):'—')+'</div></div>'+
|
|
614
846
|
'</div></div>';
|
|
615
847
|
if(evals.length){
|
|
616
848
|
h+='<div class="section"><div class="section-head"><span>Evals</span></div><table><tbody>';
|
|
@@ -634,7 +866,12 @@ async function selectTask(id){
|
|
|
634
866
|
h+='</div>';
|
|
635
867
|
el.innerHTML=h;
|
|
636
868
|
}catch(e){el.innerHTML='<div class="detail-empty">Error: '+esc(e.message)+'</div>'}
|
|
637
|
-
|
|
869
|
+
// Move the selection highlight in place — a full list refetch just to move
|
|
870
|
+
// a CSS class was the dashboard's most expensive click.
|
|
871
|
+
if(document.getElementById('panel-work').classList.contains('visible')){
|
|
872
|
+
document.querySelectorAll('.work-row.selected').forEach(r=>r.classList.remove('selected'));
|
|
873
|
+
const row=document.getElementById('wr-'+id);if(row)row.classList.add('selected');
|
|
874
|
+
} else loadTasks(taskOffset);
|
|
638
875
|
}
|
|
639
876
|
|
|
640
877
|
async function loadScorecard(){
|
|
@@ -681,7 +918,7 @@ async function loadRecommend(){
|
|
|
681
918
|
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
919
|
for(const s of list){
|
|
683
920
|
const isBest=s===best;
|
|
684
|
-
h+='<tr'+(isBest?' style="background:
|
|
921
|
+
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
922
|
}
|
|
686
923
|
h+='</tbody></table></div>';
|
|
687
924
|
}
|
|
@@ -697,8 +934,12 @@ async function loadRecommend(){
|
|
|
697
934
|
|
|
698
935
|
Colour is by node kind; radius by degree, so the derived hubs (a file many
|
|
699
936
|
tasks touched, a tool used everywhere) are the ones that read as structural. */
|
|
700
|
-
|
|
701
|
-
|
|
937
|
+
// Canvas 2D can't resolve var() at draw time, so node colors are read off
|
|
938
|
+
// :root once — every value is a design token, keeping the file hex-free
|
|
939
|
+
// outside the :root mirror (design/verify-tokens-sync.py).
|
|
940
|
+
const cssTok=n=>getComputedStyle(document.documentElement).getPropertyValue(n).trim();
|
|
941
|
+
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'),subagent:cssTok('--ui-cyan'),tool:cssTok('--ui-attn'),file:cssTok('--ink-300')};
|
|
942
|
+
const G_EDGE={contains:'rgba(148,163,184,0.35)',continues:'rgba(97,102,224,0.75)',produced:'rgba(234,179,8,0.30)',delegated:'rgba(34,211,238,0.55)',used:'rgba(249,115,22,0.30)',touched:'rgba(148,163,184,0.25)'};
|
|
702
943
|
|
|
703
944
|
async function loadGraph(){
|
|
704
945
|
const stats=document.getElementById('g-stats');
|
|
@@ -810,12 +1051,12 @@ function startGraphSim(nodes,edges){
|
|
|
810
1051
|
ctx.beginPath();ctx.moveTo(l.s.x,l.s.y);ctx.lineTo(l.t.x,l.t.y);ctx.stroke();
|
|
811
1052
|
}
|
|
812
1053
|
for(const n of nodes){
|
|
813
|
-
ctx.fillStyle=G_COLORS[n.kind]||
|
|
1054
|
+
ctx.fillStyle=G_COLORS[n.kind]||G_COLORS.file;
|
|
814
1055
|
ctx.globalAlpha=hover&&hover!==n?0.55:1;
|
|
815
1056
|
ctx.beginPath();ctx.arc(n.x,n.y,n.r,0,6.2832);ctx.fill();
|
|
816
1057
|
// Label only what carries the structure, else the canvas is unreadable.
|
|
817
1058
|
if(n.r>7||n===hover){
|
|
818
|
-
ctx.globalAlpha=1;ctx.fillStyle='
|
|
1059
|
+
ctx.globalAlpha=1;ctx.fillStyle=cssTok('--ui-tty-text');ctx.font='10px ui-monospace,monospace';
|
|
819
1060
|
ctx.fillText(String(n.label||'').slice(0,26),n.x+n.r+3,n.y+3);
|
|
820
1061
|
}
|
|
821
1062
|
ctx.globalAlpha=1;
|
|
@@ -832,12 +1073,13 @@ function showTabById(n){
|
|
|
832
1073
|
if(btn)btn.click();
|
|
833
1074
|
}
|
|
834
1075
|
|
|
835
|
-
loadActivity();loadRuns();loadRecommend();loadScorecard();loadCategories();
|
|
1076
|
+
loadWork(0);loadActivity();loadRuns();loadRecommend();loadScorecard();loadCategories();
|
|
836
1077
|
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.
|
|
1078
|
+
// Tasks/Work refresh only while their tab is up — the query is server-paged and
|
|
1079
|
+
// there is no reason to run it against the store every 15s in the background.
|
|
839
1080
|
setInterval(()=>{if(document.getElementById('panel-tasks').classList.contains('visible'))loadTasks(taskOffset)},15000);
|
|
840
1081
|
setInterval(()=>{if(document.getElementById('panel-activity').classList.contains('visible'))loadActivity()},15000);
|
|
1082
|
+
setInterval(()=>{if(document.getElementById('panel-work').classList.contains('visible'))loadWork(workOffset)},15000);
|
|
841
1083
|
</script>
|
|
842
1084
|
</body>
|
|
843
1085
|
</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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAojCD,CAAC;AACT,CAAC"}
|
package/dist/apme/graph.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Derived on demand, never persisted: the containment spine comes straight from
|
|
6
6
|
* foreign keys, while session / project / model / agent / tool / file hubs are
|
|
7
|
-
* materialized from denormalized columns and
|
|
7
|
+
* materialized from denormalized columns and trajectory payloads. Those hubs are the
|
|
8
8
|
* reason this exists — they are the only edges that connect work units sharing
|
|
9
9
|
* no ancestor.
|
|
10
10
|
*/
|
package/dist/apme/graph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/apme/graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACuC,cAAc,EAChE,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,gBAAgB;IAC/B;oDACgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;2EACuE;IACvE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;2DACuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD;;uEAEuE;AACvE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAYzF;AAED;;;;;;;;;;;+DAW+D;AAC/D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAO7E;
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/apme/graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACuC,cAAc,EAChE,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,gBAAgB;IAC/B;oDACgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;2EACuE;IACvE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;2DACuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD;;uEAEuE;AACvE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAYzF;AAED;;;;;;;;;;;+DAW+D;AAC/D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAO7E;AA+ED,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,GAAE,gBAAqB,GAAG,cAAc,CAwI5F"}
|
package/dist/apme/graph.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Derived on demand, never persisted: the containment spine comes straight from
|
|
6
6
|
* foreign keys, while session / project / model / agent / tool / file hubs are
|
|
7
|
-
* materialized from denormalized columns and
|
|
7
|
+
* materialized from denormalized columns and trajectory payloads. Those hubs are the
|
|
8
8
|
* reason this exists — they are the only edges that connect work units sharing
|
|
9
9
|
* no ancestor.
|
|
10
10
|
*/
|
|
@@ -68,6 +68,8 @@ class GraphBuilder {
|
|
|
68
68
|
existing.ts = n.ts;
|
|
69
69
|
if (existing.score == null && n.score != null)
|
|
70
70
|
existing.score = n.score;
|
|
71
|
+
if (n.meta)
|
|
72
|
+
existing.meta = { ...(existing.meta ?? {}), ...n.meta };
|
|
71
73
|
return n.id;
|
|
72
74
|
}
|
|
73
75
|
this.nodes.set(n.id, n);
|
|
@@ -125,6 +127,17 @@ function num(v) {
|
|
|
125
127
|
function str(v) {
|
|
126
128
|
return typeof v === 'string' && v.length > 0 ? v : null;
|
|
127
129
|
}
|
|
130
|
+
function payloadObject(payload) {
|
|
131
|
+
if (!payload)
|
|
132
|
+
return {};
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(payload);
|
|
135
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
128
141
|
/** Build a graph slice anchored on the most recent task units. */
|
|
129
142
|
export function buildApmeGraph(store, opts = {}) {
|
|
130
143
|
const limit = Math.min(Math.max(opts.limit ?? 60, 1), 400);
|
|
@@ -179,7 +192,44 @@ export function buildApmeGraph(store, opts = {}) {
|
|
|
179
192
|
}
|
|
180
193
|
// ── Trajectory: tools and files ──
|
|
181
194
|
const events = store.listSampleEventRows(t.id);
|
|
195
|
+
const delegated = new Set();
|
|
182
196
|
for (const ev of events) {
|
|
197
|
+
if (ev.kind === 'subagent') {
|
|
198
|
+
const p = payloadObject(ev.payload);
|
|
199
|
+
const childId = str(p.id);
|
|
200
|
+
const name = str(p.name);
|
|
201
|
+
if (!childId || !name)
|
|
202
|
+
continue;
|
|
203
|
+
let anchor = taskNode;
|
|
204
|
+
const turnId = str(ev.turnId);
|
|
205
|
+
if (includeTurns && turnId) {
|
|
206
|
+
anchor = g.node({
|
|
207
|
+
id: nid.turn(turnId), kind: 'turn',
|
|
208
|
+
label: `turn ${ev.turnIndex ?? '?'}`,
|
|
209
|
+
ts: ev.ts,
|
|
210
|
+
meta: { index: num(ev.turnIndex) },
|
|
211
|
+
});
|
|
212
|
+
g.edge(taskNode, anchor, 'contains');
|
|
213
|
+
}
|
|
214
|
+
const childKey = `${t.sessionId}:${childId}`;
|
|
215
|
+
const childNode = g.node({
|
|
216
|
+
id: nid.subagent(childKey), kind: 'subagent', label: name, ts: ev.ts,
|
|
217
|
+
meta: {
|
|
218
|
+
phase: p.phase === 'completed' ? 'completed' : 'started',
|
|
219
|
+
durationMs: num(p.durationMs),
|
|
220
|
+
summary: str(p.summary),
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
// Start + completion are two observations of ONE delegation edge, not
|
|
224
|
+
// a weighted edge of two. A completion without a captured start still
|
|
225
|
+
// gets the edge rather than disappearing.
|
|
226
|
+
const edgeKey = `${anchor}|${childNode}`;
|
|
227
|
+
if (!delegated.has(edgeKey)) {
|
|
228
|
+
delegated.add(edgeKey);
|
|
229
|
+
g.edge(anchor, childNode, 'delegated');
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
183
233
|
if (ev.kind !== 'tool')
|
|
184
234
|
continue;
|
|
185
235
|
toolEvents++;
|